// 获取首页窗口对象，阿里数字工场web版，不能直接使用window.top
// function getTopWindow(win) {
// 	if (!win) win = window;
// 	if (win.isJkTopWindow) return win;
// 	var pWindow = win.parent;
// 	if (pWindow === win) {
// 		return win;
// 	}
// 	return getTopWindow(pWindow);
// }
// window.topWindow = getTopWindow();
// window.databaseIntercept = null;
// window.isInCef = (typeof window.set_token !== 'undefined');
// if (typeof md5 === "function") $.md5 = md5;
// window.versionDetail = window.jk_get_version ? window.jk_get_version() : {}

// Element.prototype.append = Element.prototype.appendChild;
// if (!Object.entries) {
// 	Object.entries = function(obj) {
// 		var ownProps = Object.keys(obj),
// 			i = ownProps.length,
// 			resArray = new Array(i); // preallocate the Array
// 		while (i--) {
// 			resArray[i] = [ownProps[i], obj[ownProps[i]]];
// 		}
// 		return resArray;
// 	};
// }

/**
 * @author: Zhouqi
 * @desc: 快捷键策略集
 * @param {Object} tabData
 * @return {Function} 策略函数
 */
function shortcutKeyStrategies(tabData) {
	const

		tabContainer = tabData.tabContainer,
		tabs = tabData.tabs,
		activeTab = tabData.activeTab,
		e = tabData.e,
		keyCode = [16, 17, 18].includes(e.keyCode) ? '' : e.keyCode,
		ctrlKey = e.ctrlKey ? "c" : "",
		shiftKey = e.shiftKey ? "b" : "",
		altKey = e.altKey ? "a" : "",
		extraKey = !e.ctrlKey && !e.shiftKey && !e.altKey ? 'n' : '';

	let copyKeyInfos = mini.clone(topWindow.jkUtils.keyboard.infos.filter(_ => _.edit)); //系统快捷键

	//公共业务快捷键
	let commonBusinessInfos = mini.clone(topWindow.jkUtils.keyboard.businessInfos).filter(info => info.module ===
		'common' && info.edit);

	copyKeyInfos = copyKeyInfos.concat(commonBusinessInfos);

	let eventKeyCode = extraKey + ctrlKey + shiftKey + altKey + keyCode;

	let keyEvent = copyKeyInfos.find(_ => _.code === eventKeyCode);

	if (keyEvent) {
		return getShortcutKeyStrategies({
			code: keyEvent.funcCode,
			tabContainer,
			tabs,
			activeTab
		})
	}

	// 常用菜单快捷键
	let commonMenuKeyEvent = topWindow.jkUtils.keyboard.commonMenuInfos.find(item => item.moduleCode && item.code ===
		eventKeyCode);

	if (commonMenuKeyEvent) {
		return () => {
			const {
				moduleCode,
				isNavMenu,
				parentId
			} = commonMenuKeyEvent
			let tabObj = isNavMenu && parentId ? {
				moduleId: parentId,
				params: {
					moduleId: moduleCode
				}
			} : {
				moduleId: moduleCode
			};
			// window.top.jkMainFramePage.openMainTab(
			// 			// 	Object.assign({
			// 			// 		type: 'menu'
			// 			// 	}, tabObj)
			// 			// )
		}
	}

}

function getShortcutKeyStrategies({
	code,
	tabContainer,
	tabs,
	activeTab
}) {
	const strategies = {
		// Ctrl+Shift+Tab：向左切换标签页
		"1": () => {
			let tabIndex = tabs.findIndex(item => item.name === activeTab.name);
			tabIndex < 0 || tabIndex >= tabs.length - 1 ? tabIndex = 0 : tabIndex++;
			tabContainer.activeTab(tabs[tabIndex]);
		},
		// Ctrl+Tab；向右切换标签页
		"2": () => {
			let tabIndex = tabs.findIndex(item => item.name === activeTab.name);
			tabIndex <= 0 ? tabIndex = tabs.length - 1 : tabIndex--;
			tabContainer.activeTab(tabs[tabIndex]);
		},
		// Ctrl+T：再开一个当前标签页
		"3": () => {
			const moduleInfo = activeTab.moduleInfo;
			if (!moduleInfo) {
				return;
			}
			jkMainFramePage.openMainTab({
				type: moduleInfo.type,
				moduleId: moduleInfo.moduleId,
				params: activeTab.params,
				isClone: true
			});
		},
		// Ctrl+N：再开一个新的标签窗口
		"5": () => {
			const moduleInfo = activeTab.moduleInfo;
			if (!moduleInfo || !topWindow.isInCef) {
				return;
			}
			const tabEditPool = jkMainFramePage.tabEditPool;
			const index = tabEditPool.findIndex(t =>
				t.moduleId === activeTab.moduleInfo.moduleId &&
				t.cloneIndex === activeTab.cloneIndex
			);
			if (index >= 0) {
				mini.MessageBox.show({
					width: 400,
					maxWidth: 400,
					height: 260,
					title: "提示",
					buttons: ["ok", "cancel"],
					message: "您当前页面正在编辑中，确定要在新窗口中打开吗？<br /><span style='line-height: 20px; margin-top: 10px; font-size: 13px; color: #666;'>在新窗口中打开，编辑的内容将丢失</span>",
					iconCls: "mini-messagebox-question",
					callback: function(action) {
						if (action === "ok") {
							jkMainFramePage.openMultiWindow(moduleInfo);
							tabEditPool.splice(index, 1);
							tabContainer.removeTab(activeTab);
							tabContainer.fire("updateTabNum", activeTab);
						}
					}
				});
				return;
			}
			jkMainFramePage.openMultiWindow(moduleInfo);
			tabContainer.removeTab(activeTab);
			tabContainer.fire("updateTabNum", activeTab);
		},
		// Ctrl+Q：上一个使用过的标签页和当前使用的标签页进行切换
		"4": () => {
			const lastActiveTab = jkMainFramePage.recentTabManager.getLastActiveTab();
			lastActiveTab && tabContainer.activeTab(lastActiveTab);
		},
		// Ctrl+R：恢复之前关闭的标签页
		"6": () => {
			const lastdeletedTab = jkMainFramePage.recentDeletedTabManager.getLastDeletedTabs();
			if (!lastdeletedTab) {
				return;
			}
			const isArray = Array.isArray(lastdeletedTab);
			if (isArray) {
				lastdeletedTab.forEach(tab => {
					openTab(tab);
				});
				return;
			}
			openTab(lastdeletedTab);

			function openTab(tab) {
				const moduleInfo = tab.moduleInfo;
				if (!moduleInfo) {
					return;
				}
				jkMainFramePage.openMainTab({
					type: moduleInfo.type,
					moduleId: moduleInfo.moduleId,
					title: moduleInfo.moduleName,
					url: moduleInfo.moduleUrl,
					params: moduleInfo.params,
					isClone: true,
				});
			}
		},
		// Ctrl+H：关闭当前标签页
		"7": () => {
			if (activeTab === tabContainer.getTab(0)) {
				return;
			}
			jkUtils.createAndDispatchEvent("page-before-close", activeTab)
				.then(function() {
					tabContainer.removeTab(activeTab);
					jkMainFramePage.removeTabsInfo(activeTab);
					tabContainer.fire("updateTabNum", activeTab);
					jkMainFramePage.recentDeletedTabManager.update(activeTab);
				});
		},
		// Ctrl+L：锁屏
		"9": () => {
			topWindow.isInCef && !topWindow.isLocked && lock();
		},
		// Ctrl+M：本地设置
		"10": () => {
			topWindow.isInCef && topWindow.jkMainFramePage.openMenuFunc("local_config");
		},
		// Ctrl+K：定位到搜索框
		"8": () => {
			$('#search-input').focus();
		},
		//左侧筛选组件 触发筛选按钮点击事件
		"21": () => {
			let btns;
			if (activeTab.moduleInfo && activeTab.moduleInfo.hasNavMenu) {
				findIframe();
			} else {
				btns = $(activeTab._iframeEl).contents().find('#btns #submit');
				btns.length ? (btns[0].click()) : findIframe();
			}

			function findIframe() {
				let activeIframe = $(activeTab._iframeEl).contents().find('iframe');
				for (let i = 0; i < activeIframe.length; i++) {
					if ($(activeIframe[i]).css('display') !== 'none') {
						btns = $(activeIframe[i]).contents().find('#btns #submit');
						btns.length && btns[0].click();
					}
				}
			}
		}
	};
	return strategies[code];
}



window.jkUtils = {
	jkIsArray: Array.isArray,
	jkIsString: (str) => typeof str === 'string',
	getModulePageConfig(key, configId) {
		// 根据操作名获取自定义配置
		const moduleInfo = this.jkGetModuleInfo();
		if (!moduleInfo) return null;
		const pageConfigData = moduleInfo.pageConfigData;
		if (!pageConfigData) return null;
		const configs = pageConfigData[key];
		if (!configs) return null;
		if (Array.isArray(configs) && configId) return configs.find(item => item.configId === configId);
		return configs;
	},
	drag: (element, limitRect, offset = 0) => {
		if (!element) return;
		let isDragging = false;
		let currentX, currentY, initialX, initialY;
		let xOffset = 0;
		let yOffset = 0;

		const {
			left: elementLeft,
			top: elementTop,
			right: elementRight,
			bottom: elementBottom
		} = element.getBoundingClientRect();
		const {
			left: limitLeft,
			top: limitTop,
			right: limitRight,
			bottom: limitBottom
		} = limitRect || {};

		const dragStart = (e) => {
			initialX = e.clientX - xOffset;
			initialY = e.clientY - yOffset;
			e.target === element && (isDragging = true);
		}
		const drag = (e) => {
			if (isDragging) {
				e.preventDefault();
				currentX = e.clientX - initialX;
				currentY = e.clientY - initialY;
				xOffset = currentX;
				yOffset = currentY;
				if (limitRect) {
					currentX + elementLeft <= limitLeft - offset ?
						currentX = limitLeft - elementLeft - offset :
						currentX + elementRight >= limitRight + offset ?
						currentX = limitRight - elementRight + offset :
						currentX;
					currentY + elementTop <= limitTop ?
						currentY = limitTop - elementTop :
						currentY + elementBottom >= limitBottom ?
						currentY = limitBottom - elementBottom :
						currentY;
				}
				setTranslate(currentX, currentY, element);
			}
		}
		const dragEnd = () => {
			initialX = currentX;
			initialY = currentY;
			isDragging = false;
		}
		const setTranslate = (xPos, yPos, el) => {
			el.style.transform = `translate(${xPos}px, ${yPos}px)`;
		}

		// 添加鼠标按下事件监听器
		document.addEventListener("mousedown", dragStart);
		// 添加鼠标移动事件监听器
		document.addEventListener("mousemove", drag);
		// 添加鼠标松开事件监听器
		document.addEventListener("mouseup", dragEnd);
	},
	checkMenuPermission(moduleId) {
		const allMenus = topWindow.allMenus;
		if (!Array.isArray(allMenus)) return false;
		return !!allMenus.find(item => item.moduleId === moduleId);
	},
	// 模糊搜索组件
	JKFuzzySearch: function({
		containerSelector,
		option,
		width = 160,
		onSearch
	}) {
		this.containerSelector = containerSelector;
		this.fuzzyCombobox = null;
		this.fuzzyTextbox = null;
		this.onSearch = onSearch;
		this.containerWidth = width + 48;
		this.height = 24;
		this.buttonWidth = 24;

		// 初始化
		this.init = () => {
			const combobox = createCombobox();
			// 获取容器
			const container = normalizeContainer(this.containerSelector);
			const searchViewEl = createSearchView(combobox);
			const fuzzySearchContainer = createFuzzyContainer();
			fuzzySearchContainer.append(searchViewEl);
			fuzzySearchContainer.append(combobox.getEl());
			container.appendChild(fuzzySearchContainer[0]);
		};

		// 创建模糊搜索组件外层容器
		const createFuzzyContainer = () => {
			const fuzzySearchContainer = $('<div></div>');
			fuzzySearchContainer.css({
				position: 'relative',
				width: this.containerWidth,
				border: 'solid 1px #cfdadd',
				height: this.height
			});
			return fuzzySearchContainer;
		};

		// 创建下拉组件
		const createCombobox = () => {
			const combobox = this.fuzzyCombobox = new JKControl.JKComboBox();
			// 传入用户的下拉组件配置
			combobox.set(option);
			const comboboxEl = combobox.getEl();
			$(comboboxEl).css({
				borderRadius: 0,
				height: this.height,
				width: this.containerWidth,
				border: 0
			});
			// 绑定valuechanged事件，当下拉选项改变时，更新textbox的emptyText
			combobox.on('valuechanged', () => {
				const text = combobox.getText();
				this.fuzzyTextbox.setEmptyText(text.replace(',', '/'));
			});
			return combobox;
		};

		// 创建搜索框
		const createSearchView = () => {
			const textboxEl = createTextBox();
			const searchButtonEl = createSearchButton();
			const searchBoxView = $('<div></div>');
			searchBoxView.css({
				display: 'flex',
				alignItems: 'center',
			});
			const slideButtonEl = createSlideButton();
			searchBoxView.append(slideButtonEl);
			searchBoxView.append(textboxEl);
			searchBoxView.append(searchButtonEl);
			// 通过绝对定位将底下的下拉框组件遮住
			searchBoxView.css({
				position: 'absolute',
				zIndex: 100
			});
			return searchBoxView;
		}

		// 创建下拉框按钮
		const createSlideButton = () => {
			const slideButtonEl = $('<span></span>');
			slideButtonEl.css({
				display: 'inline-block',
				width: this.buttonWidth,
				height: this.height,
				cursor: 'pointer',
				background: 'url(/styles/css/images/sprites/css_sprites.png) no-repeat -261px -143px #ffffff'
			});
			// 控制下拉框的显示和隐藏
			let defaultShow = false;
			slideButtonEl.off('click').on('click', (e) => {
				e.stopPropagation();
				if (defaultShow) {
					defaultShow = false;
					return;
				}
				this.fuzzyCombobox.showPopup();
				defaultShow = true;
			});
			// 点击到下拉框外部时会隐藏下拉框的popup，此时需要将defaultShow标记为false
			$(document).on('click', () => {
				defaultShow = false;
			});
			// 点击到下拉框的popup时，需要将defaultShow标记为true
			$(this.fuzzyCombobox.popup.el).on('click', (e) => {
				e.stopPropagation();
				defaultShow = true;
			})
			return slideButtonEl;
		};

		// 创建文本框
		const createTextBox = () => {
			const textbox = this.fuzzyTextbox = new JKControl.JKTextBox();
			const textboxEl = textbox.getEl();
			$(textbox._borderEl).css({
				border: 0,
				borderRadius: 0,
				padding: 0
			});
			$(textboxEl).css({
				borderRadius: 0,
				height: this.height,
				width
			});
			// 按下enter键触发搜索方法
			textbox.on('keydown', (e) => {
				const event = e.htmlEvent;
				(event.keyCode === 13 || event.key === 'Enter') && fireSearch();
			});
			return textboxEl;
		}

		// 创建搜索按钮
		const createSearchButton = () => {
			const searchButton = $('<span><i class="fa fa-search"></i></span>');
			searchButton.css({
				display: 'inline-block',
				width: this.buttonWidth,
				height: this.height,
				textAlign: 'center',
				lineHeight: '24px',
				backgroundColor: '#fff',
				cursor: 'pointer',
				color: '#b6b9c1',
				fontSize: 16,
			});
			// 给搜索按钮绑定点击事件
			searchButton.on('click', fireSearch);
			return searchButton;
		};

		// 触发搜索
		const fireSearch = () => {
			typeof this.onSearch === 'function' && this.onSearch(this.fuzzyTextbox.getValue(), this
				.fuzzyCombobox.getValue());
		}

		// 校验容器
		const normalizeContainer = (containerSelector) => {
			const container = document.getElementById(containerSelector);
			if (!container) {
				throw new Error('containerSelector is not exist');
			}
			return container;
		}
	},
	errorWebLog: {
		errorStack: new Set(),
		getErrorModuleInfo(prop) {
			try {
				const moduleInfo = jkUtils.jkGetModuleInfo();
				if (!moduleInfo) return "unknown";
				if (moduleInfo.moduleId === "mainframe") return "mainframe";
				return moduleInfo[prop];
			} catch (e) {
				return "unknown";
			}
		},
		inspectLogInWeb(subType, content, extraData) {
			// web版数据上报
			const {
				user_name: userName,
				member_name: memberName,
			} = topWindow.loginInfo;
			const {
				networkIp: hostIp,
				clientSn: clientId,
				deviceName: device
			} = topWindow.loginResult;
			const errorData = {
				subType,
				data: {
					timestamp: Date.now(),
					userName,
					memberName,
					hostIp,
					content: 'base64:' + topWindow.Base64.encode(content),
					extraData: 'base64:' + topWindow.Base64.encode(extraData),
					clientId,
					terminal: '',
					device
				}
			}
			const errorStack = topWindow.jkUtils.errorWebLog.errorStack;
			errorStack.add(errorData);
			if (errorStack.size < 20) return;
			jkUtils.errorWebLog.sendBeaconData();
		},
		sendBeaconData() {
			const errorStack = topWindow.jkUtils.errorWebLog.errorStack;
			const recordString = JSON.stringify([...errorStack]);
			const recordData = {
				record: recordString
			};
			const signData = jkUtils.jkGetSign(recordData, {}, false);
			const data = new FormData();
			Reflect.ownKeys(signData).forEach(key => {
				data.append(key, signData[key]);
			});
			const sendResult = navigator.sendBeacon('/jkyun/basic-function/qualityMonitor/record', data);
			if (sendResult) {
				errorStack.clear();
				return;
			}
			// sendBeacon没有发送成功的话使用ajax请求
			jkUtils.jkAjax({
				url: "/jkyun/basic-function/qualityMonitor/record",
				type: "post",
				data: recordData
			}).done(() => {
				errorStack.clear();
			});
		}
	},
	jkDebounce(func, wait, options) {
		/* global globalThis, self */
		const freeGlobal = typeof global === 'object' && global !== null && global.Object === Object && global

		/** Detect free variable `globalThis` */
		const freeGlobalThis = typeof globalThis === 'object' && globalThis !== null && globalThis.Object ==
			Object && globalThis

		/** Detect free variable `self`. */
		const freeSelf = typeof self === 'object' && self !== null && self.Object === Object && self

		/** Used as a reference to the global object. */
		const root = freeGlobalThis || freeGlobal || freeSelf || Function('return this')()

		let lastArgs,
			lastThis,
			maxWait,
			result,
			timerId,
			lastCallTime

		let lastInvokeTime = 0
		let leading = false
		let maxing = false
		let trailing = true

		// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
		const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')

		if (typeof func !== 'function') {
			throw new TypeError('Expected a function')
		}
		wait = +wait || 0
		// if (isObject(options)) {
		leading = !!options.leading
		maxing = 'maxWait' in options
		maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
		trailing = 'trailing' in options ? !!options.trailing : trailing
		// }

		function invokeFunc(time) {
			const args = lastArgs
			const thisArg = lastThis

			lastArgs = lastThis = undefined
			lastInvokeTime = time
			result = func.apply(thisArg, args)
			return result
		}

		function startTimer(pendingFunc, wait) {
			if (useRAF) {
				root.cancelAnimationFrame(timerId)
				return root.requestAnimationFrame(pendingFunc)
			}
			return setTimeout(pendingFunc, wait)
		}

		function cancelTimer(id) {
			if (useRAF) {
				return root.cancelAnimationFrame(id)
			}
			clearTimeout(id)
		}

		function leadingEdge(time) {
			// Reset any `maxWait` timer.
			lastInvokeTime = time
			// Start the timer for the trailing edge.
			timerId = startTimer(timerExpired, wait)
			// Invoke the leading edge.
			return leading ? invokeFunc(time) : result
		}

		function remainingWait(time) {
			const timeSinceLastCall = time - lastCallTime
			const timeSinceLastInvoke = time - lastInvokeTime
			const timeWaiting = wait - timeSinceLastCall

			return maxing ?
				Math.min(timeWaiting, maxWait - timeSinceLastInvoke) :
				timeWaiting
		}

		function shouldInvoke(time) {
			const timeSinceLastCall = time - lastCallTime
			const timeSinceLastInvoke = time - lastInvokeTime

			// Either this is the first call, activity has stopped and we're at the
			// trailing edge, the system time has gone backwards and we're treating
			// it as the trailing edge, or we've hit the `maxWait` limit.
			return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
				(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
		}

		function timerExpired() {
			const time = Date.now()
			if (shouldInvoke(time)) {
				return trailingEdge(time)
			}
			// Restart the timer.
			timerId = startTimer(timerExpired, remainingWait(time))
		}

		function trailingEdge(time) {
			timerId = undefined

			// Only invoke if we have `lastArgs` which means `func` has been
			// debounced at least once.
			if (trailing && lastArgs) {
				return invokeFunc(time)
			}
			lastArgs = lastThis = undefined
			return result
		}

		function cancel() {
			if (timerId !== undefined) {
				cancelTimer(timerId)
			}
			lastInvokeTime = 0
			lastArgs = lastCallTime = lastThis = timerId = undefined
		}

		function flush() {
			return timerId === undefined ? result : trailingEdge(Date.now())
		}

		function pending() {
			return timerId !== undefined
		}

		function debounced(...args) {
			const time = Date.now()
			const isInvoking = shouldInvoke(time)

			lastArgs = args
			lastThis = this
			lastCallTime = time

			if (isInvoking) {
				if (timerId === undefined) {
					return leadingEdge(lastCallTime)
				}
				if (maxing) {
					// Handle invocations in a tight loop.
					timerId = startTimer(timerExpired, wait)
					return invokeFunc(lastCallTime)
				}
			}
			if (timerId === undefined) {
				timerId = startTimer(timerExpired, wait)
			}
			return result
		}
		debounced.cancel = cancel
		debounced.flush = flush
		debounced.pending = pending
		return debounced
	},
	jkIsObject: (obj) => Object.prototype.toString.call(obj) === '[object Object]',
	jkIsFunction: (func) => typeof func === 'function',
	runExcelUploader() {
		if (isInCef) {
			try {
				window.jk_runExcelUploader();
			} catch (error) {}
		}
	},
	gzip(data) {
		try {
			if (!data) return data;
			const str = encodeURIComponent(JSON.stringify(data));
			const binaryString = topWindow.pako.gzip(str);
			let s = "";
			Array.from(binaryString).forEach((item) => {
				s += String.fromCharCode(item)
			});
			return 'base64:' + topWindow.Base64.btoa(s);
		} catch (error) {
			return data;
		}
	},
	ungzip(b64Data) {
		try {
			// 解决数据过大和中文乱码
			const strData = topWindow.Base64.atob(b64Data);
			const charData = strData.split('').map((x) => x.charCodeAt(0));
			const binData = new Uint8Array(charData);
			const data = topWindow.pako.inflate(binData);
			const array = new Uint16Array(data);
			let res = '';
			const chunk = 8 * 1024;
			// 大数据量大的情况下String.fromCharCode处理可能会报错，这里进行切片分批处理数据
			let i;
			for (i = 0; i < array.length / chunk; i++) {
				res += String.fromCharCode.apply(null, array.slice(i * chunk, (i + 1) * chunk));
			}
			res += String.fromCharCode.apply(null, array.slice(i * chunk));
			// 后端编码可能会出现中文乱码，这里需要用decodeURIComponent进行解码
			res = decodeURIComponent(res);
			return res;
		} catch (error) {
			const moduleId = jkUtils.errorWebLog.getErrorModuleInfo('moduleId');
			const
				errorContent = {
					stack: '筛选条件gzip解析失败',
					filename: window.location.pathname,
					lineno: 1,
					colno: 1,
					pathname: window.location.pathname,
					moduleName: moduleId
				},
				extra = {
					fileName: window.location.pathname,
					linkFile: window.location.pathname,
					moduleId,
					errorId: Date.now()
				}
			jkUtils.inspectWeblog('1', JSON.stringify(errorContent), JSON.stringify(extra));
			return '[]';
		}
	},
	chooseSalesman(tradeNoList = [], tradeIdList = [], cb) {
		if (!tradeNoList.length) {
			jkUtils.showTip({
				state: 'warning',
				content: '未选择订单'
			})
			return
		}
		mini.open({
			targetWindow: window,
			url: '/common/chooseSalesman/user.html',
			title: '选择吉密操作员',
			width: 430,
			height: 400,
			allowResize: false,
			showModal: true,
			ondestroy({
				state,
				action
			}) {
				if (state !== 'success') {
					return
				}
				const {
					userSelected,
					notice,
					validity,
					isSendSMS
				} = action
				let userInfo;
				try {
					const {
						member_name,
						jdHost,
						pddHost
					} = jkUtils.getLoginInfo()
					userInfo = JSON.stringify({
						member_name,
						jdHost,
						pddHost
					})
				} catch (error) {
					userInfo = ''
				}
				const data = {
					tradeNoList: JSON.stringify(tradeNoList),
					tradeIdList: JSON.stringify(tradeIdList),
					userId: JSON.stringify(userSelected.map(item => item.userId)),
					remark: notice,
					validityDay: validity,
					isSendSMS,
					userInfo
				}

				jkUtils.jkAjax({
					type: 'post',
					url: '/jkyun/share-service/jm-service/pushOrderToJimi',
					data
				}).then(() => {
					cb && cb(tradeNoList, tradeIdList)
				})
			}
		})
	},
	/**
	 * @author: Wangming
	 * @description: 获取对象深层次属性
	 * @param {Object} object 原对象
	 * @param {String} attributeChain 获取属性的字符串
	 * @param {any} defaultValue 取不到目标属性时的默认值
	 * @return {any}
	 */
	// 例 const data = get(res, 'result.data', []) 返回结果和res.result.data一致，但是可以避免result为undefined的报错，并且赋予返回的一个为一个空数组
	get(object, attributeChain, defaultValue = null) {
		if (!object || typeof object !== 'object') {
			return defaultValue;
		}

		const attributes = attributeChain
			.replace(/\[/g, '.')
			.replace(/\]/g, '')
			.split('.')
			.filter(item => item);
		let isValid = true;
		let returnVal = object;

		for (let i = 0; i < attributes.length; i++) {
			if (!returnVal || Object.keys(returnVal).indexOf(attributes[i]) < 0) {
				isValid = false;
				break;
			}
			returnVal = returnVal[attributes[i]];
		}

		return isValid ? returnVal : defaultValue;
	},
	/**
	 * @author: Zhouqi
	 * @desc: 判断文本中是否含有emoji
	 * @param {string} substring 字符串文本
	 * @returns {Boolean} 是否含有emoji
	 */
	hasEmojiCharacter(text) {
		if (!text) return false;
		const isSupportUnicode = (new RegExp()).unicode;
		let unicodeReg;
		if (isSupportUnicode === false) {
			unicodeReg = new RegExp(
				"[\\ud83c\\udf00-\\ud83d\\uddff]|[\\ud83d\\ude00-\\ud83e\\ude4f]|[\\ud83d\\ude80-\\ud83d\\udefa]|[\\u2600-\\u26ff]|[\\ud800\\udc00-\\udbff\\udfff]",
				"gu");
		} else {
			unicodeReg = new RegExp("[\\u2600-\\u26ff]", "g");
		}
		const match = text.match(unicodeReg);
		return !!(match && match.length)
	},
	/**
	 * @author: Zhouqi
	 * @desc: 快捷键功能
	 * @param {Object} e 事件参数对象
	 * @param {Object} tabContainer minitab
	 */
	changeTabByShortcutKey(tabContainer, e) {
		if (!tabContainer) {
			return;
		}
		const tabs = tabContainer.tabs;
		const activeTab = tabContainer.getActiveTab();
		if (!tabs.length || !activeTab) {
			return;
		}
		const data = {
			e,
			tabs,
			activeTab,
			tabContainer
		}
		const shortcutKeyStrategyFunc = shortcutKeyStrategies(data);
		shortcutKeyStrategyFunc && shortcutKeyStrategyFunc();
	},
	/**
	 * @author: Zhouqi
	 * @desc: 打开并直接定位到即时通讯系统中对应的用户聊天框
	 * @param {Object} userInfo 用户信息，包含用户id和用户真实姓名
	 */
	locateImUser(userInfo) {
		if (isInCef && typeof window.jk_imRun === 'function') {
			jkUtils.jkAjax({
				url: '/jkyun/oa-im/im/dataIntegration/runIm',
				type: 'post'
			}).then(() => {
				try {
					if (userInfo) {
						window.jk_imRun(JSON.stringify(userInfo));
						return;
					}
					window.jk_imRun();
				} catch (e) {
					console.log(e);
				}
			}).fail((e) => {
				console.log(e);
			});
		}
	},
	math: {
		add: function(a, b) {
			const factor = Math.pow(10, Math.max(
				(a.toString().split('.')[1]?.length || 0),
				(b.toString().split('.')[1]?.length || 0)
			));

			return (Math.round(a * factor) + Math.round(b * factor)) / factor;
		},

		sub: function(a, b) {
			var c, d, e;
			try {
				c = a.toString().split('.')[1].length;
			} catch (f) {
				c = 0;
			}
			try {
				d = b.toString().split('.')[1].length;
			} catch (f) {
				d = 0;
			}
			return e = Math.pow(10, Math.max(c, d)), (this.mul(a, e) - this.mul(b, e)) / e;
		},

		mul: function(a, b) {
			var c = 0,
				d = a.toString(),
				e = b.toString();
			try {
				c += d.split('.')[1].length;
			} catch (f) {}
			try {
				c += e.split('.')[1].length;
			} catch (f) {}
			return Number(d.replace('.', '')) * Number(e.replace('.', '')) / Math.pow(10, c);
		},

		div: function(a, b) {
			var c, d, e = 0,
				f = 0;
			try {
				e = a.toString().split('.')[1].length;
			} catch (g) {}
			try {
				f = b.toString().split('.')[1].length;
			} catch (g) {}
			return c = Number(a.toString().replace('.', '')), d = Number(b.toString().replace('.', '')), this
				.mul(c / d, Math.pow(10, f - e));
		}
	},
	cookie: {
		setCookie: function(key, val, days, basePath) {
			if (!days || !$.isNumeric(days)) days = 1;
			var d = new Date();
			d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));

			var expires = 'expires=' + d.toUTCString(),
				path = basePath === 'basePath' ? ';path=/' : '';
			document.cookie = key + '=' + val + '; ' + expires + path;
		},

		getCookie: function(key) {
			if (document.cookie.length > 0) {
				var cookies = {},
					arr = document.cookie.split(';');
				arr.forEach(a => {
					var p = a.split('='),
						k = p[0].trim(),
						v = p[1];
					cookies[k] = v;
				});
				if (cookies[key]) {
					return cookies[key];
				}
			}
			return '';
		}
	},
	// 判断是否在开发环境，不包含集成环境
	isInJustDev: function() {
		var hostname = window.location.hostname;
		var __hostname__ = window.location.__hostname__;
		return (hostname === 'localhost') || (hostname === '127.0.0.1') ||
			(hostname === '192.168.88.51' || hostname === 'dev.jackyun.com' || __hostname__ ===
				'dev.jackyun.com');
	},
	//是否在开发环境，包含集成测试环境
	isInDev: function() {
		var hostname = window.location.hostname;
		return (hostname === 'localhost') || (hostname === '127.0.0.1') ||
			(hostname === '192.168.88.51' || hostname === 'dev.jackyun.com') ||
			(hostname === '192.168.88.186:31088' || hostname === 'test.jackyun.com');
	},
	// 是否吉客云内部系统
	isInnerSys: function() {
		let enableSn = ["jackyun_dev", "jackyun", "420001", "134139", "420002", "430002", "440001", "450001",
			"460008", "666666", "10114103", "55555555", "18800233", "66666666", "61001", "470002"
		];
		if (enableSn.indexOf(topWindow.userinfo.memberName) >= 0) {
			return true;
		}
		return false;
	},
	isInJkyunCef: function() {
		if (isInCef) {
			let version = JSON.parse(versionDetail);
			if (version.wdgj == 0 || !topWindow.isHighversion) return true;
			if (window.login_site === 'login_site') return true;
		}
		return localStorage.getItem("login_site") === 'jackyun';
	},
	frontStyleVersion: function() {
		const JStyleList = [
			'flagship',
			'universal',
			'Yuncang',
		]
		let loginInfo = topWindow.loginInfo || jkUtils.getLoginInfo();
		if (JStyleList.includes(loginInfo.loginResult.softVersion)) {
			return 'Jkyun_J' // j系列风格
		}
		return 'Jkyun' // 吉客云风格
	},
	// 是否为吉客云全能版
	isStandardVersion: function() {
		const JStyleList = [
			'flagship',
			'universal',
			'Yuncang',
		]
		let loginInfo = topWindow.loginInfo || jkUtils.getLoginInfo();
		return !JStyleList.includes(loginInfo.loginResult.softVersion);
	},
	//判断是否是网店管家J系列迁移会员
	isJkyun_J: function() {
		let loginInfo = topWindow.loginInfo || jkUtils.getLoginInfo();
		const plate = topWindow.jkMainFramePage && topWindow.jkMainFramePage.plate || topWindow.plate;
		if (plate === "jackyun_wdgj" && loginInfo.system_ip.indexOf("login.jackyun.com") !== -1) {
			return true;
		} else {
			return false;
		}
	},
	//判断是否是请求PDD的域名
	isPddRequest: function(url) {
		let loginInfo = topWindow.loginInfo || jkUtils.getLoginInfo();
		let pddHost = loginInfo.pddHost;
		if (url.indexOf(pddHost) !== -1) return true;
		else return false;
	},
	//前端生成GUID
	newGuid: function() {
		var guid = '';
		for (var i = 1; i <= 32; i++) {
			var n = Math.floor(Math.random() * 16.0).toString(16);
			guid += n;
			if ((i === 8) || (i === 12) || (i === 16) || (i === 20))
				guid += '-';
		}
		return guid;
	},
	//生成时间戳相关的唯一ID值
	getUniqueId: function() {
		var now = new Date(),
			nowStamp = now.valueOf(),
			randomNum = Math.floor(Math.random() * 100);
		return nowStamp + "" + randomNum;
	},
	//获取window.location.href参数
	getQueryString: function(name, containChinese) {
		var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
		var r = window.location.search.substr(1).match(reg);
		if (r != null) return containChinese ? decodeURI(r[2]) : unescape(r[2]);
		return null;
	},
	// 获取链接url的参数
	getUrlParam: function(url, name, containChinese) {
		var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"),
			arr = url.split('?');
		if (arr.length <= 1) return null;
		var r = arr[1].match(reg);
		if (r != null) return containChinese ? decodeURI(r[2]) : unescape(r[2]);
		return null;
	},
	// 文本溢出显示title，ctn表示有text-overflow: ellipsis样式的元素，el表示要显示title的元素
	setEllipsis: function(ctn, el) {
		if (!el) el = ctn;
		var c = ctn[0].clientWidth,
			s = ctn[0].scrollWidth;
		if (c < s) el.attr('title', el.text());
	},
	//获取属性.属性.属性形式的值
	getDTOValue: function(data, field) {
		if (!field) {
			return '';
		}
		var fields = field.split('.');
		var value = data[fields[0]];
		for (var i = 1; i < fields.length; i++) {
			try {
				value = value[fields[i]];
			} catch (error) {
				value = '';
			}
		}
		return value;
	},
	//权限控制方法抽出放入utils中
	checkFunCode: function(code) {
		if (jkUtils.isInJustDev()) {
			return true;
		}
		//如果当前页面没有权限信息,则从localStorage中读取权限表至当前页面
		if (!window.authorityObject) {
			try {
				var authorityStorage = window.localStorage;
				let authorityJSON = authorityStorage.getItem('authorityControl');
				if (authorityJSON == null) {
					return true;
				}
				let authorityObject = JSON.parse(authorityJSON);

				if (!(authorityObject && authorityObject.data && Array.isArray(authorityObject.data)))
					return false;
				window.authorityObject = authorityObject;
			} catch (error) {
				window.authorityObject = [];
			}
		}

		let arrayLength = window.authorityObject.data.length;
		//如果权限码在权限表中, 则直接返回true, 不再进行后续遍历
		for (var i = 0; i < arrayLength; i++) {
			if (window.authorityObject.data[i].funCode === code) {
				return true;
			}
		}
		return false;
	},
	//字符串转驼峰法
	tranformCamelStr: function(str, split) {
		var strArr = str.split(split);
		for (var i = 1; i < strArr.length; i++) {
			strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1);
		}
		return strArr.join('');
	},
	clearUnicodeSymbols: function(text) {
		/**
		 *用以过滤常见的unicode字符
		 *注意: u模式是ES6语法, 低版本浏览器无法支持, 所以这里先进行unicode属性检测
		 *在低版本情况下降级, 不匹配四字节的unicode字符
		 *[\ud83c\udf00-\ud83d\uddff] symbols & pictographs
		 *[\ud83d\ude00-\ud83d\ude4f] emoticons
		 *[\ud83d\ude80-\ud83d\udefa] transport & map symbol
		 *[\u2000-\u206f] General Punctuation
		 *[\u2600-\u26ff] other
		 */
		if (text === undefined || text === null) {
			return '';
		}
		if (!jkUtils.jkIsString(text)) return text;
		var isSupportUnicode = (new RegExp()).unicode;
		var unicodeReg;
		if (isSupportUnicode === false) {
			unicodeReg = new RegExp(
				"[\\ud83c\\udf00-\\ud83d\\uddff]|[\\ud83d\\ude00-\\ud83e\\ude4f]|[\\ud83d\\ude80-\\ud83d\\udefa]|[\\u2600-\\u26ff]|[\\ud800\\udc00-\\udbff\\udfff]",
				"gu");
		} else {
			unicodeReg = new RegExp("[\\u2600-\\u26ff]", "g");
		}
		return text.replace(unicodeReg, "");
	},
	//新增解决XSS相关问题的方法
	XSS: {
		//字符串转义字符表
		characterEscapeList: {
			'<': '&lt;',
			'>': '&gt;',
			'&': '&amp;',
			'"': '&quot;',
			"'": '&#39;'
		},

		options: {
			whiteList: {
				a: ['target', 'href', 'title', 'class', 'style'],
				abbr: ['title', 'class', 'style'],
				address: ['class', 'style'],
				area: ['shape', 'coords', 'href', 'alt'],
				article: [],
				aside: [],
				audio: ['autoplay', 'controls', 'loop', 'preload', 'src', 'class', 'style'],
				b: ['class', 'style'],
				bdi: ['dir'],
				bdo: ['dir'],
				big: [],
				blockquote: ['cite', 'class', 'style'],
				br: [],
				caption: ['class', 'style'],
				center: [],
				cite: [],
				code: ['class', 'style'],
				col: ['align', 'valign', 'span', 'width', 'class', 'style'],
				colgroup: ['align', 'valign', 'span', 'width', 'class', 'style'],
				dd: ['class', 'style'],
				del: ['datetime'],
				details: ['open'],
				div: ['class', 'style', 'url', 'link'],
				dl: ['class', 'style'],
				dt: ['class', 'style'],
				em: ['class', 'style'],
				font: ['color', 'size', 'face'],
				footer: [],
				h1: ['class', 'style'],
				h2: ['class', 'style'],
				h3: ['class', 'style'],
				h4: ['class', 'style'],
				h5: ['class', 'style'],
				h6: ['class', 'style'],
				header: [],
				hr: [],
				i: ['class', 'style'],
				img: ['src', 'alt', 'title', 'width', 'height', 'id', '_src', 'loadingclass', 'class',
					'data-latex'
				],
				ins: ['datetime'],
				li: ['class', 'style'],
				mark: [],
				nav: [],
				ol: ['class', 'style'],
				p: ['class', 'style'],
				pre: ['class', 'style'],
				s: [],
				section: [],
				small: [],
				span: ['class', 'style', 'type', 'moduleid', 'params', 'link'],
				sub: ['class', 'style'],
				sup: ['class', 'style'],
				strong: ['class', 'style'],
				table: ['width', 'border', 'align', 'valign', 'class', 'style'],
				tbody: ['align', 'valign', 'class', 'style'],
				td: ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],
				tfoot: ['align', 'valign', 'class', 'style'],
				th: ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],
				thead: ['align', 'valign', 'class', 'style'],
				tr: ['rowspan', 'align', 'valign', 'class', 'style'],
				tt: [],
				u: [],
				ul: ['class', 'style'],
				video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width', 'class', 'style'],
				canvas: ['height', 'width']
			}
		},

		//创建反转义字符表(即将上述转义字符表倒置)
		__createUnescapList: function() {
			let obj = this.characterEscapeList;
			this.characterUnescapeList = Object.keys(obj).reduce((origin, key) => {
				origin[obj[key]] = key;
				return origin;
			}, {});
		},

		//js特殊字符转义, 用以处理输入内容可能存在特殊标签的情况
		characterEscape: function(text) {
			if (typeof text === 'string' && typeof window.filterXSS === 'function') {
				text = filterXSS(text, this.options);
			}
			return text;
		},

		//js特殊字符的反转义, 用以处理成转义字符的字符串内容回显为转义前的内容
		characterUnescape: function(text) {
			//反转义需要用到反转义表, 如果反转义表不存在, 则先生成反转义表
			if (!this.characterUnescapeList) {
				this.__createUnescapList();
			}
			if (text) {
				//从上方反转义字符表中获取要转义的键字符串
				var keyCharacterStr = Object.keys(this.characterUnescapeList).toString();
				//需要转义的键字符串用|符号分割, 以便符合生成正则表达式的规则
				keyCharacterStr = keyCharacterStr.replace(/,/g, '|');
				//生成匹配所用的正则对象
				var regExp = new RegExp(keyCharacterStr, 'g');
				//用转义字符表中的值替代匹配结果
				var text = text.replace(regExp, match => this.characterUnescapeList[match]);
			}
			return text;
		}
	},
	jk_lang: {
		language: '',
		// 设置语言模式
		jkSetLanguage: function(lang) {
			if (window.jk_web_setconfig) {
				window.jk_web_setconfig('language', lang);
			} else {
				jkUtils.localStorage.set({
					key: 'language',
					value: lang
				});
			}
			jkUtils.jk_lang.language = lang;
		},

		// 获取语言模式
		jkGetLanguage: function() {
			var lang = jkUtils.jk_lang.language;
			if (!lang) {
				if (window.jk_web_getconfig) {
					lang = window.jk_web_getconfig('language');
				} else {
					lang = jkUtils.localStorage.get('language');
				}
				jkUtils.jk_lang.language = lang;
			}
			return lang;
		},
	},
	jkIsMenuStatic: function(fileNames) {
		return fileNames?.includes('menu') ?
			'https://jkyun-static.oss-cn-hangzhou.aliyuncs.com/devops/menuGlobal/test-jackyun/' :
			'https://jkyun-static.oss-cn-hangzhou.aliyuncs.com/devops/interfaceStatistic/test-jackyun/'
	},
	// html页面dom元素替换文本为相应语言，fileName为翻译文件文件名，必传
	/**
	 * newMode：是否使用新翻译模式，在中文环境下，非定制账号不需要请求翻译资源，但是老的业务组代码里面部分
	 * 组件或者页面翻译也依赖中文环境下的翻译资源，并且没有填写默认的中文值，只有翻译的 code，这部分需要进行修改
	 * 为了不影响未改造的老页面，这里对 jki18nHtml 添加额外的参数  newMode，如果该值为 true，则使用新的翻译方式，
	 * 即中文环境下不参与翻译，当业务组对老页面完成改造后可以该配置改成 true
	 */
	jki18nHtml: function(fileNames, newMode = false) {
		if (!fileNames) return;
		let customFileNames = mini.clone(fileNames);
		const shouldGetTranslateResource = topWindow.shouldGetTranslateResource;
		if (typeof fileNames == 'string') fileNames = [fileNames];
		if (window.langFileName) fileNames = fileNames.filter(f => window.langFileName.indexOf(f) <
			0); // 过滤已经加载过的翻译资源
		var language = topWindow.language;
		if (!language) {
			language = 'zh_CN';
			jkUtils.jk_lang.jkSetLanguage(language);
		}

		function setTranslate() {
			language !== 'zh_CN' && $('[i18n]').each(function() {
				const code = $(this).attr('i18n');
				if (window.langMap[code]) $(this).text(window.langMap[code]);
			});
			newMode ?
				$('[tooltip]').each(function() {
					const code = $(this).attr('data-i18nCode');
					const tooltip = window.langMap[code];
					tooltip && $(this).attr('tooltip', tooltip);
				}) : $('[tooltip]').each(function() {
					const code = $(this).attr('tooltip');
					const dataField = $(this).attr('data-field');
					const tooltip = window.langMap[code] || dataField;
					if (tooltip) $(this).attr('tooltip', tooltip);
				});
			newMode ?
				$('[title]').each(function() {
					const code = $(this).attr('data-i18nCode');
					const title = window.langMap[code];
					title && $(this).attr('title', title);
				}) : $('[title]').each(function() {
					const code = $(this).attr('title');
					const dataField = $(this).attr('data-field');
					window.langMap[code] ? $(this).attr('title', window.langMap[code]) : (dataField && $(
						this).attr('title', dataField) || code);
				});
			newMode ?
				$('[emptyText]').each(function() {
					const code = $(this).attr('data-i18nCode');
					const emptyText = window.langMap[code];
					emptyText && $(this).attr('emptyText', emptyText);
				}) : $('[emptyText]').each(function() {
					const code = $(this).attr('emptyText');
					if (window.langMap[code]) $(this).attr('emptyText', window.langMap[code]);
				});
		}
		let settings = {
			name: fileNames,
			path: jkUtils.jkIsMenuStatic(fileNames), // 可填写本地/远程地址,例如配置为path:'/jkyun/i18n/'代理地址
			mode: 'map', // 模式采用map结构
			language: language,
			cache: true, // 根据最终确定的获取语言方式修改
			async: false, // 是否异步方式获取多语言配置文件，默认同步方式
			callback: function() {
				window.langMap = $.i18n.map;
				if (window.langFileName) {
					window.langFileName = window.langFileName.concat(fileNames);
				} else {
					window.langFileName = fileNames;
				}
				if (shouldGetTranslateResource) jkUtils.jkCustomi18nHtml(customFileNames);
				setTranslate();
			}
		};
		if (fileNames.length > 0) {
			if (newMode) {
				(language !== 'zh_CN' || shouldGetTranslateResource) && jQuery.i18n.properties(settings);
				return;
			}
			jQuery.i18n.properties(settings);
			return;
		}
		if (shouldGetTranslateResource) jkUtils.jkCustomi18nHtml(customFileNames);
		setTranslate();
	},
	jkCustomi18nHtml(fileName) {
		const language = jkUtils.jk_lang.jkGetLanguage();
		const fileNames = topWindow.customClientCount;
		if (!fileNames) return;
		if (window.langFileName && ~window.langFileName.indexOf(`static_${fileName}_${fileNames}`)) return;
		let settings = {
			name: fileNames,
			path: '/i18n/custom/',
			language,
			mode: 'map',
			cache: true,
			async: false,
			callback: function() {
				window.langMap = $.i18n.map;
				window.langFileName = window.langFileName.concat(`static_${fileName}_${fileNames}`);
			}
		};
		jQuery.i18n.properties(settings);
	},
	// 多用于处理js内部文字替换，i18nCode为要翻译的文本在翻译文件内的关键字，defaultText为翻译失败时显示默认文本，fileName为翻译文件文件名，非必传
	jki18n: function(i18nCode, defaultText, fileNames) {
		let customFileNames = mini.clone(fileNames);
		const shouldGetTranslateResource = topWindow.shouldGetTranslateResource;
		var language = jkUtils.jk_lang.jkGetLanguage();
		if (!language || language === 'zh_CN' && (!shouldGetTranslateResource || fileNames === 'menu'))
			return defaultText; // 没有语言翻译功能时显示默认值
		if (!window.langFileName && !fileNames) return;
		if (fileNames && typeof fileNames == 'string') fileNames = [fileNames]; // 默认将资源文件转为数组
		if (window.langFileName && fileNames) fileNames = fileNames.filter(f => window.langFileName.indexOf(f) <
			0); // 过滤已经加载过的翻译资源
		let i18nResult = null;
		let settings = {
			name: fileNames,
			path: jkUtils.jkIsMenuStatic(fileNames), // 可填写本地/远程地址,例如配置为path:'/jkyun/i18n/'代理地址
			language: language, // 根据最终确定的获取语言方式修改
			mode: 'map',
			cache: true,
			async: false, // 是否异步方式获取多语言配置文件，默认同步方式
			callback: function() {
				window.langMap = $.i18n.map;
				if (window.langFileName) {
					window.langFileName = window.langFileName.concat(fileNames);
				} else {
					window.langFileName = fileNames;
				}
				i18nResult = window.langMap[i18nCode];
				if (shouldGetTranslateResource) i18nResult = jkUtils.jkCustomTranslate(i18nCode,
					defaultText, customFileNames);
			}
		};
		if (fileNames && fileNames.length > 0) {
			jQuery.i18n.properties(settings);
		} else {
			i18nResult = shouldGetTranslateResource ? jkUtils.jkCustomTranslate(i18nCode, defaultText,
				customFileNames) : window.langMap[i18nCode];
		}

		if (!i18nResult) i18nResult = defaultText;
		return i18nResult;
	},
	jkCustomTranslate(i18nCode, defaultText, fileName) {
		const language = jkUtils.jk_lang.jkGetLanguage();
		if (!window.langFileName) return window.langMap[i18nCode] || defaultText;
		const fileNames = topWindow.customClientCount;
		if (!fileNames) return window.langMap[i18nCode] || defaultText;
		if (window.langFileName && ~window.langFileName.indexOf(`static_${fileName}_${fileNames}`)) return window
			.langMap[i18nCode] || defaultText;
		let i18nResult = null;
		let settings = {
			name: fileNames,
			path: '/i18n/custom/',
			language,
			mode: 'map',
			cache: true,
			async: false,
			callback: function() {
				window.langMap = $.i18n.map;
				window.langFileName = window.langFileName.concat(`static_${fileName}_${fileNames}`);
				i18nResult = window.langMap[i18nCode];
			}
		};
		jQuery.i18n.properties(settings);
		if (!i18nResult) i18nResult = defaultText;
		return i18nResult;
	},
	jkGridCustomTranslate: function(code, defaultText) {
		if (window.gridLangMap) return window.gridLangMap[code] || defaultText || code;
		const fileNames = topWindow.customClientCount;
		if (!fileNames) return code;
		const lang = jkUtils.jk_lang.jkGetLanguage();
		if (!lang) return code;

		let result = code;
		let settings = {
			name: fileNames,
			type: 'dynamic',
			path: '/i18n/custom/', // 可填写本地/远程地址,例如配置为path:'/jkyun/i18n/'代理地址
			language: lang, // 根据最终确定的获取语言方式修改
			mode: 'map',
			cache: true,
			async: false, // 是否异步方式获取多语言配置文件，默认同步方式
			callback: function() {
				window.gridLangMap = $.i18n.map;
				result = window.gridLangMap[code] || code;
			}
		};
		jQuery.i18n.properties(settings);

		return result
	},
	jkGridTranslate: function(code, defaultText, defaultModuleCode) {
		// 是否是3级定制用户
		const shouldGetTranslateResource = topWindow.shouldGetTranslateResource == 3;
		const lang = jkUtils.jk_lang.jkGetLanguage();
		// 中文状态下如果是定制用户，直接取定制资源
		if (shouldGetTranslateResource && (!lang || lang === 'zh_CN')) return jkUtils.jkGridCustomTranslate(
			code, defaultText);
		const localConfigs = topWindow.localConfigs
		// 如果定制用户开启了动态列翻译，则先取动态列翻译资源，再取定制资源。如果没开启动态列翻译直接取定制资源
		if (!localConfigs || !+localConfigs.gridTranslate) return shouldGetTranslateResource ? jkUtils
			.jkGridCustomTranslate(code, defaultText) : (defaultText || code)
		if (window.gridLangMap) return window.gridLangMap[code] || code;
		const moduleCode = defaultModuleCode || jkUtils.jkGetModuleCode() || 'basic'
		if (!lang || lang === 'zh_CN') return code;

		let result = code;
		let settings = {
			name: moduleCode,
			type: 'dynamic',
			path: '/i18n/', // 可填写本地/远程地址,例如配置为path:'/jkyun/i18n/'代理地址
			language: lang, // 根据最终确定的获取语言方式修改
			mode: 'map',
			cache: true,
			async: false, // 是否异步方式获取多语言配置文件，默认同步方式
			callback: function() {
				window.gridLangMap = $.i18n.map;
				result = window.gridLangMap[code] || code;
				if (shouldGetTranslateResource) result = jkUtils.jkGridCustomTranslate(code,
					defaultText);
			}
		};
		if (moduleCode) jQuery.i18n.properties(settings);

		return result
	},
	jkDynamicTranslate: function(code, defaultText, defaultModuleCode) {
		const lang = jkUtils.jk_lang.jkGetLanguage();
		if (window.dynamicLangMap) return window.dynamicLangMap[code] || defaultText || code;
		const moduleCode = defaultModuleCode || jkUtils.jkGetModuleCode() || 'basic'
		if (!lang || lang === 'zh_CN') return defaultText || code;

		let result = defaultText || code;
		let settings = {
			name: moduleCode,
			type: 'dynamic',
			path: '/i18n/', // 可填写本地/远程地址,例如配置为path:'/jkyun/i18n/'代理地址
			language: lang, // 根据最终确定的获取语言方式修改
			mode: 'map',
			cache: true,
			async: false, // 是否异步方式获取多语言配置文件，默认同步方式
			callback: function() {
				window.dynamicLangMap = $.i18n.map;
				result = window.dynamicLangMap[code] || defaultText || code;
			}
		};
		if (moduleCode) jQuery.i18n.properties(settings);

		return result
	},
	jkGetModuleConfigWithAsync: function(moduleCode) {
		var dtd = $.Deferred();
		setTimeout(function() {
			try {
				var r = window.jk_get_persetting(moduleCode, "strict");
				if (r)
					dtd.resolve(JSON.parse(r));
				else
					dtd.resolve({});
			} catch (e) {
				jkUtils.writeLogToShell(e);
				dtd.resolve({});
			}
		}, 2000);
		return dtd;
	},
	// 获取模块页面自定义配置
	jkGetModuleConfig: function(moduleCode, useErrorHandler) {
		if (!isInCef || (typeof window.jk_get_persetting !== "function")) {
			return jkUtils.jkAjax({
				url: '/jkyun/erp/system/getCustomConfigList',
				type: 'get',
				data: {
					moduleCode: moduleCode,
				},
				useErrorHandler: useErrorHandler
			}).then(function(result) {
				return result.data;
			});
		}
		try {
			return JSON.parse(window.jk_get_persetting(moduleCode, "strict"));
		} catch (e) {
			jkUtils.writeLogToShell(e);
			//return [];
		}
	},
	//保存用户自定义配置
	//参数可以传入下
	//actionName, configs必须传
	//customModuleCode 目前只用于主界面，普通菜单模块不用传该值
	//callback 设置成功后的回调函数
	//isShowTip，是否显示配置成功后的提示消息
	//realTime 是否需要实时上传oss，默认false
	jkSaveCustomDefined: function(actionName, configs, realTime = false) {
		var callback;
		var customModuleCode;
		var isShowTip = true;
		for (var i = 2; i < arguments.length; i++) {
			if (typeof arguments[i] === 'function') callback = arguments[i];
			else if (typeof arguments[i] === 'string') customModuleCode = arguments[i];
			else if (typeof arguments[i] === 'boolean') isShowTip = arguments[i];
		}

		var moduleInfo = this.jkGetModuleInfo();
		var moduleCode = (moduleInfo) ? moduleInfo.moduleIdWithCode : customModuleCode;
		if (customModuleCode) moduleCode = customModuleCode;
		var v = {};
		v.params = {};
		v.params.moduleCode = moduleCode;
		v.params.actionName = actionName;
		v.params.configInfo = configs;
		if (window.jk_set_persetting) {
			function saveCallback(isOK, msg) {
				if (isOK) {
					if (moduleInfo && moduleInfo.moduleConfig) {
						var list = (moduleInfo.moduleConfig.configList) ? moduleInfo.moduleConfig.configList :
							moduleInfo.moduleConfig;
						var flag = false;
						list.forEach(function(item) {
							if (item.actionName === actionName) {
								item.configInfo = configs;
								flag = true;
							}
						});
						if (!flag) {
							var n = {};
							n.actionName = actionName;
							n.configInfo = configs;
							list.push(n);
						}
					}
					//moduleInfo.moduleConfig.configList = list;
					var params = {};
					if (isShowTip) {
						params.content = msg;
						jkUtils.showTip(params);
					}
					if (typeof callback !== 'undefined') callback();
				}
			}
			if (realTime) {
				window.jk_set_persetting(moduleCode, actionName, JSON.stringify(configs), saveCallback,
					"immediately");
			} else {
				window.jk_set_persetting(moduleCode, actionName, JSON.stringify(configs), saveCallback);
			}
		} else {
			return jkUtils.jkAjax({
				url: '/jkyun/erp/system/saveUserConfig',
				type: 'post',
				data: v
			}).done((result, msg) => {
				if (moduleInfo && moduleInfo.moduleConfig) {
					var list = moduleInfo.moduleConfig.configList;
					var flag = false;
					list.forEach(function(item) {
						if (item.actionName === actionName) {
							item.configInfo = configs;
							flag = true;
						}
					});
					if (!flag) {
						var n = {};
						n.actionName = actionName;
						n.configInfo = configs;
						list.push(n);
					}
				}
				//moduleInfo.moduleConfig.configList = list;
				var params = {};
				if (isShowTip) {
					params.content = msg;
					jkUtils.showTip(params);
				}
				if (typeof callback !== 'undefined') callback();
			});
		}
	},
	//根据操作名获取用户定义配置，因为模块启动的时候会把该模块下所有配置文件获取过来，所以只要传操作名就可以
	jkGetCustomDefined: function(actionName) {
		var moduleInfo = this.jkGetModuleInfo();
		if (moduleInfo === null) return null;

		var moduleConfig;
		if (moduleInfo.moduleConfig) {
			moduleConfig = (moduleInfo.moduleConfig.configList) ? moduleInfo.moduleConfig.configList :
				moduleInfo.moduleConfig;
		}

		var configs = [];
		if (moduleConfig) {
			configs = moduleConfig.filter(function(item) {
				return (item.actionName === actionName);
			});
		}

		if (window.jk_get_persetting) {
			return (configs.length === 0) ? null :
				(typeof configs[0].configInfo === 'string') ? JSON.parse(configs[0].configInfo) : configs[0]
				.configInfo;
		} else
			return (configs.length === 0) ? null : configs[0].configInfo;
	},
	// 获取首页窗口对象，阿里数字工场web版，不能直接使用window.top
	jkGetTopWindow: function(win) {
		return getTopWindow(win);
	},
	jkGetParentWindow: function(window, level) {
		// 这个方法就是获取顶层窗口，其实可以直接用window.top来表示，不需要调用这个方法
		if (window.isJkTopWindow) return window;
		var pWindow = window.parent;
		var l = (typeof level === 'number') ? level : 999;
		while ((typeof pWindow !== 'undefined') && (l > 0)) {
			if (pWindow.isJkTopWindow) return pWindow;
			pWindow = pWindow.parent;
			l = l - 1;
			if (pWindow === pWindow.parent) break;
		}
		return pWindow;
	},
	// 获取适合弹窗的target窗口
	jkGetFitPopTargetWindow: function(window, limitX, limitY) {
		if ((typeof limitX !== 'number') || (typeof limitY !== 'number')) return window;
		if (window === topWindow) return window;
		var pWidth = $(window).width(),
			pHeight = $(window).height();
		if (pWidth < limitX || pHeight < limitY) {
			var targetWindow = arguments.callee(window.parent);
			// 父页面上如果不存在mini，则还是用本页面的window
			return targetWindow.mini ? targetWindow : window;
		}
		return window;
	},
	// 首页获取ccs配置的方法
	jkGetCCSConfigInMainFrame: function(moduleCode) {
		let promise = new Promise((resolve, reject) => {
			let ajaxCallback = function(res) {
				try {
					// 解析结果数据
					let datas = JSON.parse(res.data.cfg_data)[0].moduleData;
					if (!$.isArray(datas)) datas = [];
					// ccs真实配置都存在configInfo中，后端返回的是json字符串，解析一下成对象，方便使用
					datas.forEach(data => {
						data.configInfo = JSON.parse(data.configInfo);
					});
					resolve(datas);
				} catch (error) {
					jkUtils.writeLogToShell(`线上CCS获取配置异常: ${JSON.stringify(error)}`);
					reject(error);
				}
			}
			jkUtils.jkAjax({
				url: "/jkyun/ccs/getCfgInfo",
				data: {
					moduleCode: moduleCode,
					needManagerCfg: true,
				},
				forceUseErrorHandler: false
			}).then(function(res) {
				ajaxCallback(res);
			}).fail(err => {
				reject(err);
			});
		});
		return promise;
	},
	// 获取模块（菜单页/导航子菜单页）的ccs配置
	// moduleCode tab页中保存的moduleInfo中的moduleIdWidthCode
	// actionName 页面局部的操作名，例如左侧有左侧的actionName
	// forceInterface 只有首页会使用
	jkGetCCSConfig: function(moduleCode, actionName, forceInterface) {
		let promise = new Promise((resolve, reject) => {
			let configs;
			let ajaxCallback = function(res) {
				try {
					// 解析结果数据
					let datas = JSON.parse(res.data.cfg_data)[0].moduleData;
					if (!$.isArray(datas)) datas = [];
					// ccs真实配置都存在configInfo中，后端返回的是json字符串，解析一下成对象，方便使用
					datas.forEach(data => {
						if (typeof data.configInfo === 'string') {
							const isBase64Data = data.configInfo.indexOf('base64:') === 0;
							data.configInfo = isBase64Data ? JSON.parse(jkUtils.ungzip(data
								.configInfo.slice(7))) : JSON.parse(data.configInfo);
						}
					});

					// 不传actionName，表示获取的是整个模块页面的配置
					if (!actionName) {
						resolve(datas);
						return;
					}
					// 传了actionName，表示获取模块页面指定区域的局部配置
					let data = datas.find(c => c.actionName === actionName);
					if (!data) {
						resolve(null);
						return;
					}
					configs = data.configInfo;
					resolve(configs);
				} catch (error) {
					jkUtils.writeLogToShell(`线上CCS获取配置异常: ${JSON.stringify(error)}`);
					reject(error);
				}
			}
			// web版获取ccs | 客户端启动首页会强制从接口获取
			if (forceInterface || !isInCef || (typeof window.jk_get_persetting !== "function")) {
				jkUtils.jkAjax({
					url: "/jkyun/ccs/getCfgInfo",
					data: {
						moduleCode: moduleCode,
						needManagerCfg: true,
					},
					forceUseErrorHandler: false
				}).then(function(res) {
					ajaxCallback(res);
				}).fail(err => {
					reject(err);
				});
				return;
			}
			// 客户端接口拿数据
			try {
				let res = JSON.parse(window.jk_get_persetting(moduleCode, "strict"));
				// ccs优化改造后，数据格式换成了object
				if (Object.prototype.toString.call(res) === "[object Object]") {
					// 获取成功，客户端会返回一个标记位
					if (res.state !== "success") {
						jkUtils.jkAjax({
							url: "/jkyun/ccs/getCfgInfo",
							async: false,
							data: {
								moduleCode: moduleCode,
								needManagerCfg: true,
							},
						}).then(res => {
							ajaxCallback(res);
						}).fail(err => {
							jkUtils.writeLogToShell(`线上CCS获取配置异常: ${JSON.stringify(err)}`)
							reject(err);
						});
						return;
					}
					configs = res.data;
				} else {
					configs = res;
				}
				if (!$.isArray(configs)) configs = [];
				// 拿到配置后，下面的逻辑跟web一致
				configs.forEach(config => {
					if (typeof config.configInfo === "string") {
						const isBase64Data = config.configInfo.indexOf('base64:') === 0;
						config.configInfo = isBase64Data ? JSON.parse(jkUtils.ungzip(config
							.configInfo.slice(7))) : JSON.parse(config.configInfo);
					}
				});
				if (!actionName) {
					resolve(configs);
					return;
				}
				let data = configs.find(c => c.actionName === actionName);
				if (!data) {
					resolve(null);
					return;
				}
				let config = data.configInfo;
				resolve(config);
			} catch (error) {
				jkUtils.writeLogToShell(`解析客户端获取的CCS配置: ${JSON.stringify(error)}`)
				reject(error);
			}
		});
		return promise;

	},
	// 保存ccs配置
	// config 要保存配置的页面及配置等 Ojject
	jkSaveCCSConfig: function(config, promise, realTime = false) {
		// 整个保存会返回一个异步对象
		let deferred = $.Deferred();
		let moduleInfo = jkUtils.jkGetModuleInfo(config.win);

		// 没有moduleInfo | 获取ccs配置失败时，不允许保存
		if (!moduleInfo || moduleInfo.moduleConfigState === "fail") {
			if (!isInCef) {
				jkUtils.layer({
					state: "warning",
					content: "个性化配置保存失败，请重新打开页面",
				});
				return deferred.reject();
			}
			deferred.reject();
		}

		// promise 非必传，为获取ccs配置的那个promise，如果没有，内部会自己创建一个
		if (Object.prototype.toString.call(promise) !== "[object Promise]") {
			promise = new Promise(resolve => {
				resolve();
			});
		}

		// promise成功回调才允许保存配置，防止失败也触发保存
		promise.then(() => {
			let actionName = config.actionName,
				configs = config.configs,
				moduleCode = config.moduleCode ? config.moduleCode : moduleInfo.moduleIdWithCode;

			const updateModuleInfo = () => {
				// 保存成功后更新tab中的moduleInfo中储存的配置
				if (moduleInfo.moduleConfig) {
					let flag = false;
					moduleInfo.moduleConfig.forEach(c => {
						if (c.actionName === actionName) {
							c.configInfo = configs;
							flag = true;
						}
					});
					if (!flag) {
						moduleInfo.moduleConfig.push({
							actionName: actionName,
							configInfo: configs,
						});
					}
				}
			}
			// 驾驶舱通过判断moduleInfo是否已经获取到moduleConfig来判断是否成功获取到总配置
			if (moduleInfo.moduleId === "cockpit" && !moduleInfo.moduleConfig) {
				return deferred.reject();
			}
			if (isInCef && (typeof window.jk_set_persetting === "function")) {
				try {
					function okCallback(isOk, msg) {
						if (isOk) {
							deferred.resolve(msg);
						} else {
							deferred.reject();
						}
					}
					let saveType = '';
					if (config.saveHistory) saveType = 'history';
					if (realTime) saveType = 'immediately';
					window.jk_set_persetting(moduleCode, actionName, typeof configs === 'string' ?
						configs : JSON.stringify(configs), okCallback, saveType);
				} catch (err) {
					deferred.reject(err);
				}
			} else {
				const cfgInfo = {
					moduleCode: moduleCode,
					moduleData: [{
						actionName: actionName,
						configVersion: Date.parse(new Date()), // 会添加一个时间戳，保证更新后，客户端也会更新数据
						configInfo: configs,
					}]
				}
				if (config.delayInWeb) {
					topWindow.jkSaveCCSConfigDelay && topWindow.jkSaveCCSConfigDelay(cfgInfo, deferred)
					return;
				}
				jkUtils.jkAjax({
					url: '/jkyun/ccs/updateCfg',
					type: 'post',
					unTestNet: true,
					data: {
						cfgInfo: [cfgInfo]
					},
					forceUseErrorHandler: false
				}).done((res, msg) => {
					deferred.resolve(msg);
				}).fail(err => {
					deferred.reject(err);
				});
			}
			deferred.then(msg => {
				// 保存成功后，显示成功提示
				if (config.isShowTip) {
					jkUtils.showTip({
						content: msg,
					});
				}
				updateModuleInfo();
				if (typeof config.callback === "function") config.callback();
			});
		}).catch((err) => {
			deferred.reject(err);
		});

		deferred.fail(() => {
			if (isInCef) {
				jkUtils.layer({
					state: "warning",
					content: "个性化配置保存失败，请重新打开页面",
				});
			}
		});

		return deferred;
	},
	// 获取当前页面所在 tab 的 moduleInfo
	jkGetModuleInfo: function(win) {
		var moduleInfo = null;
		win = win || window;

		// 首页没有 moduleInfo
		if (win === win.parent) return win.moduleInfo;
		// 这里改造一下，减少调用此方法的tab不是activeTab的风险
		// tab页获取 moduleInfo
		while (win !== win.parent) {
			// 驾驶舱的 moduleInfo 是驾驶舱自己创建的，这里兼容一下代码不同步，同步后可以去掉三元表达式
			if (win.moduleCode === "cockpit") {
				moduleInfo = win.moduleInfo ? win.moduleInfo : {
					moduleId: 'cockpit',
					moduleIdWithCode: 'cockpit',
				};
				break;
			}
			// tab页加载完成，可以在tab页window中获取到moduleInfo
			if (win.moduleInfo) {
				moduleInfo = win.moduleInfo;
				break;
			}
			// tab页还未加载完成，tab页中的window还没集成到moduleInfo
			// 可判定为刚打开tab，可在当前的tab中获取moduleInfo
			if (win.parent === topWindow) {
				const jkMainTabs = topWindow.jkMainTabs;
				var tab = jkMainTabs && jkMainTabs.getActiveTab();
				moduleInfo = tab && tab.moduleInfo;
				win.moduleInfo = moduleInfo;
				break;
			}
			win = win.parent;
		}

		return moduleInfo;
	},
	// 获取当前 tab 的 moduleCode (由 moduleId 和 moduleCode 组成)
	jkGetModuleCode: function() {
		var moduleInfo = this.jkGetModuleInfo();
		if (!moduleInfo) return null;
		return moduleInfo.moduleIdWithCode;
	},
	// 根据操作名获取自定义配置
	jkGetCustomConfig: function(actionName) {
		var moduleInfo = this.jkGetModuleInfo();
		if (!moduleInfo) {
			jkUtils.writeLogToShell(`moduleInfo不存在: ${JSON.stringify(moduleInfo)}`);
			return null;
		}

		var moduleConfig = moduleInfo.moduleConfig;
		if (!moduleConfig || !$.isArray(moduleConfig)) {
			jkUtils.writeLogToShell(`moduleConfig不存在: ${JSON.stringify(moduleConfig)}`);
			return null;
		}

		var configs = moduleConfig.filter(c => c.actionName === actionName);
		if (configs.length < 1) {
			jkUtils.writeLogToShell(`configs为空: ${JSON.stringify(configs)}`);
			return null;
		}

		let configInfo = configs[0].configInfo;
		if (typeof configInfo === "string") {
			try {
				const isBase64Data = configInfo.indexOf('base64:') === 0;
				configInfo = configs[0].configInfo =
					isBase64Data ? JSON.parse(jkUtils.ungzip(configInfo.slice(7))) :
					JSON.parse(configInfo);
			} catch (error) {
				jkUtils.writeLogToShell(`jkGetCustomConfig解析configInfo失败: ${JSON.stringify(error)}`);
				configInfo = configs[0].configInfo = null;
			}
		}
		return configInfo;
	},

	//判断是否在壳里
	jkInCEF: function() {
		return (typeof window.set_token !== 'undefined');
	},
	// 拷贝内容到剪切板，已解决https下复制失效的问题
	jkExecCommand: function(text) {
		function createFakerElement(text) {
			const fakerElement = document.createElement('textarea');
			fakerElement.style.position = 'absolute';
			fakerElement.style.left = '-9999px';
			fakerElement.value = text;
			document.body.appendChild(fakerElement);
			return fakerElement;
		}
		const textCopy = createFakerElement(text);
		textCopy.select();
		var finalText = getSelection().toString();
		if (finalText.length === 0) {
			return false;
		}
		document.execCommand('Copy');
		document.body.removeChild(textCopy);
		return true;
	},
	// 拷贝内容
	jkCopy: function(context) {
		var copyFlag = this.jkExecCommand(context);
		if (copyFlag) {
			this.showTip({
				content: jkUtils.jki18n('base_copy_succeed', '复制成功！', 'base'),
				state: 'success'
			});
		}
	},
	// json字符串转成json对象
	jkParseJson: function(jsonstr) {
		var res = '';
		try {
			res = JSON.parse(jsonstr);
		} catch (error) {}
		return res;
	},
	/**
	 * 前端异常记入客户端, 客户端最终发送至服务端
	 * @param type {Number} 当前日志的错误类型, 0:ajax异常, 1:前端资源加载错误, 2:前端代码错误
	 * @param moduleId {String} 异常发生的模块ID
	 * @param content {String} 日志内容(注意, 有长度限制)
	 * @param url {String} ajax请求地址
	 * @param pathname {String} ajax异常时所在的window.location.pathname
	 */
	writeErrorWebLog: function(type, moduleId, content, url, pathname) {
		if (typeof window.jk_ClientWeblog === "function") {
			try {
				window.jk_ClientWeblog(type, moduleId, content, url, pathname);
			} catch (e) {
				console.warn("写入weblog错误");
			}
		}
	},
	/**
	 * 巡检系统数据上报方法
	 * @param subType {String} 日志的错误类型, 0前端资源异常, 1前端代码异常, 2后端接口异常
	 * @param content {String} 日志内容
	 * @param extraData {String} 额外的日志内容
	 */
	inspectWeblog: function(subType, content, extraData) {
		try {
			const fileName = JSON.parse(extraData).fileName;
			if (fileName && fileName.indexOf('chrome-extension') !== -1) return;
		} catch (error) {}
		if (typeof window.jk_InspectWeblog === "function") {
			try {
				window.jk_InspectWeblog(subType, 'base64:' + topWindow.Base64.encode(content), 'base64:' +
					topWindow.Base64.encode(extraData));
			} catch (e) {
				console.warn("写入InspectWeblog错误");
			}
			return;
		}
		jkUtils.errorWebLog.inspectLogInWeb(subType, content, extraData);
	},

	//判断是否处于性能监控状态下
	isInPerformanceMonitor: function() {
		return false;
	},

	writePerformanceLog: function() {
		return false;
	},

	//从接口请求的URL中截取业务类型
	getServiceType: function(url) {
		var typeReg = /jkyun\/(.*?)\//i;
		typeReg.exec(url);
		return RegExp.$1;
	},

	/**
	 * 格式化ajax请求的日志内容
	 * 需要进行日志记录的三种情况:1.请求成功, 花费时间超过阈值 2.请求失败 3.请求成功
	 * 2020-08-13修正: 被接口过滤器拦截的接口不再记录接口信息
	 * @param options {Object} 封装的AJAX请求options对象
	 * @param time {Number} 本次请求所花费的绝对时间
	 */
	writeAjaxWeblog: function(options, duration) {
		if (options.jqXHR.statusText === "abort") {
			return;
		}
		//ajax请求URL
		var url = options.url,
			retryTimes = options.retryTimes,
			errorRetryTimes = options.errorRetryTimes,
			//ajax请求的状态码
			status = options.jqXHR.status,
			serviceType = jkUtils.getServiceType(url),
			contextId = 0,
			errorType = "interface_error",
			subType = "2",
			resultCode = 0,
			errorContent = '',
			error = null,
			response = null,
			errorId = `${window.location.pathname};${url}`,
			responseText = '',
			isSend = false;
		//接口调用失败逻辑interface_fail
		if (status !== 200) {
			resultCode = status;
			errorType = "interface_fail";
			isSend = true;
		}
		if (duration > 3000) {
			//接口调用超时interface_timeout
			resultCode = -1;
			errorType = "interface_timeout";
			isSend = true;
		}
		try {
			responseText = options.jqXHR.responseText;
			response = JSON.parse(responseText);
			if (response.code != 200) {
				resultCode = response.code;
				isSend = true;
				errorType = "interface_error";
			}
			if (response.code == 200) {
				response.result.data = "*";
			}
			if (errorRetryTimes) {
				response.errorRetryTimes = errorRetryTimes;
			}
			if (retryTimes) {
				response.retryTimes = retryTimes;
			}
			contextId = response.result.contextId;
		} catch (e) {
			response = responseText !== void 0 ? JSON.stringify(responseText).slice(0, 1024) :
				'responseText为undefined';
			contextId = 0;
		}

		errorContent = JSON.stringify(response),
			error = JSON.stringify({
				url: url,
				duration: duration,
				contextId: contextId,
				resultCode: resultCode,
				serviceType: serviceType,
				errorType: errorType,
				errorId: errorId
			});

		if (isSend) {
			this.inspectWeblog(subType, errorContent, error);
			const errorData = {
				response,
				url,
				duration,
				contextId,
				resultCode,
				serviceType,
				errorType,
				errorId
			}
			retryTimes && (errorData.retryTimes = retryTimes);
			this.writeLogToShell(`ajax异常: ${JSON.stringify(errorData)}`);
		}
	},

	// 按ascii排序字符，允许字符串和对象数组的key进行排序
	asciiSort: function(a, b, key, flag) {
		if (typeof a === 'object') {
			a = a[key];
			b = b[key];
		}
		if (typeof a === 'string') flag = key;

		if (a < b) {
			if (flag === false) return 1;
			return -1;
		}
		if (a > b) {
			if (flag === false) return -1;
			return 1;
		}
		return 0;
	},
	// 生成签名
	makeSign: function(params, secret) {
		console.log("生成签名", params, secret)
		if (!params) params = {};
		var str = '',
			_this = this;
		var arr = params.sort(function(a, b) {
			return _this.asciiSort(a, b, 'key');
		});
		arr.forEach(a => {
			str += a.key + a.value;
		});
		console.log("--", secret + str + secret)
		var sign = md5(secret + str + secret).toUpperCase();
		console.log("我是md5", sign);
		return sign;
	},
	// 包含各平台的token，appkey，secret
	signInfo: {
		// 吉客云
		jackyun: function() {
			return {
				token: "E7BFA2557AE0DD03F4D83ED5BC6930C3",
				appkey: "jackyun_web_browser_2024",
				secret: "72EyvujHoQWmjfKqsl168SaVycZARQvt",
			}
		},
		// 管家J系列
		jackyun_wdgj: function() {
			if (isInCef) {
				return {
					token: window.get_token() || "",
					appkey: "wdgj_j_client",
				}
			}
			return {
				token: jkUtils.cookie.getCookie('token') || "",
				appkey: "wdgj_j_web_browser",
				secret: "rObVimzXWyslxyqDxacwpgXpbNZAWiv6",
			}
		},
		// 数字工厂
		jackyun_ali: function() {
			return {
				token: jkUtils.cookie.getCookie('token') || "",
				appkey: "jackyun_iot",
				secret: "3MG5TnCcvJLbihKrxLvKYa4GCCVCmGQd",
			}
		},
		// 京东平台
		jd_web: function() {
			return {
				token: sessionStorage.getItem("token") || "",
				appkey: "jackyun_jd_web",
				secret: "dwJs8RD7223HDkeu2fSH7EDs",
			}
		}
	},
	// 根据输入参数获取数字签名，并和参数一期返回
	// 新增isForceJackyun参数, 该参数为true时强制使用吉客云的参数
	jkGetSign2: function(e, t, n, i) {

		console.log("====",e, t, n, i)

		let o = {},
			a = [],
			r = void 0 === n || n;
		try {
			let e = 'jackyun';
			(!0 === i || jkUtils.isInJkyunCef() && "jkyun" === topWindow.signPlate) && (e = "jackyun");
			let t = jkUtils.signInfo[e]();
			if (!0 === i && (t.secret = "app_jackyun_manager_secret"),
				!t)
				return n ? "" : null;
			var s = t.token,
				l = t.appkey,
				c = t.secret
		} catch (e) {
			return n ? "" : null
		}
		void 0 !== e && (o = $.extend(o, e));
		var d = {
			key: "appkey"
		};
		d.value = l,
			a.push(d);
		var u = {
			key: "timestamp"
		};
		u.value = String((new Date).valueOf()),
			a.push(u);
		var f = {
			key: "access_token"
		};


		for (var m in f.value = "Bearer " + (s || topWindow.loginResult.access_token || ""),
				a.push(f),
				e) {
			var p = {
					key: m
				},
				g = e[m];
			void 0 === g || "string" == typeof g && g.match(/^\s+$/) || "" === g || (/\W/g.test(m) && console
				.error("接口请求参数名key请不要使用特殊字符，可使用字母数字_"),
				p.value = "object" == typeof g ? jkUtils.jkJsonStringify(g) : String(g),
				a.push(p))
		}
		var h, k = null == t ? "" : t;
		if (isInCef && !0 !== i)
			try {
				var y = JSON.stringify({
					data: a
				});
				console.log("==hh==",h);
				// h = window.make_sign(y, k).toUpperCase()

			} catch (e) {}
		else
			h = jkUtils.makeSign(a, c, k);
		if (r) {
			var w = "timestamp=" + u.value;
			for (var j in w = (w = (w = w + "&access_token=" + f.value) + "&appkey=" + d.value) + "&sign=" + h,
					o)
				w = "object" == typeof o[j] && null !== o[j] ? w + "&" + j + "=" + String(encodeURIComponent(
					jkUtils.jkJsonStringify(o[j]))) : w + "&" + j + "=" + String(encodeURIComponent(String(o[
					j])));
						console.error("====",w)
			return w
		}


		for (var j in o)
			"object" == typeof o[j] && null !== o[j] ? o[j] = jkUtils.jkJsonStringify(o[j]) : o[j] = String(o[
				j]);
		return o.timestamp = u.value,
			o.access_token = f.value,
			o.appkey = d.value,
			o.sign = h,
			o
	},
	jkGetSign: function(urlParam, bodyParam, isReturnUrl, isForceJackyun) {
		let body = {}; // 储存最终接口请求参数
		signParmas = []; // 储存参与签名计算参数
		_isReturnUrl = true;

		var secret = {
			'value': '72EyvujHoQWmjfKqsl168SaVycZARQvt'
		};
		var _timestamp = {
			'value': Date.now()
		};
		var _access_token = {
			'value': 'Bearer 7CB3E192EA9A920029C5DC2EC2DEA4EF'
		};
		var _appkey = {
			'value': 'jackyun_web_browser_2024'
		}
		var signParmas = [{
				'key': 'appkey',
				'value': _appkey.value
			},
			{
				'key': 'timestamp',
				'value': _timestamp.value
			},
			{
				'key': 'access_token',
				'value': _access_token.value
			},
		];
		// return false;
		// 处理参与签名计算的参数
		for (var key in urlParam) {
			var p = {
				key: key
			};
			var val = urlParam[key];

			// 过滤空值
			if (typeof val === 'undefined' || (typeof val === 'string' && val.match(/^\s+$/)) || val === '')
				continue;
			if (/\W/g.test(key)) console.error('接口请求参数名key请不要使用特殊字符，可使用字母数字_');
			if (typeof val === 'object') {
				p.value = jkUtils.jkJsonStringify(val);
			} else {
				p.value = String(val);
			}
			signParmas.push(p);
		}

		var _sign, _bodyParam = (typeof bodyParam === 'undefined' || bodyParam === null) ? '' : bodyParam;
		if (isInCef && isForceJackyun !== true) {
			try {
				var _data = JSON.stringify({
					data: signParmas
				});
				// _sign = window.make_sign(_data, _bodyParam).toUpperCase();
			} catch (error) {}
		} else {
			console.log("==signParmas==", signParmas, secret, _bodyParam)
			_sign = jkUtils.makeSign(signParmas, secret, _bodyParam);
		}

		if (_isReturnUrl) {
			var s = 'timestamp=' + _timestamp.value;
			s = s + '&access_token=' + _access_token.value;
			s = s + '&appkey=' + _appkey.value;
			s = s + '&sign=' + _sign;

			for (var k in body) {
				if (typeof body[k] === 'object' && body[k] !== null) {
					s = s + '&' + k + '=' + String(encodeURIComponent(jkUtils.jkJsonStringify(body[k])));
				} else {
					s = s + '&' + k + '=' + String(encodeURIComponent(String(body[k])));
				}
			}
			console.log("=1=", s)
			return s;
		} else {
			for (var k in body) {
				if (typeof body[k] === 'object' && body[k] !== null) {
					body[k] = jkUtils.jkJsonStringify(body[k]);
				} else {
					body[k] = String(body[k]);
				}
			}
			body.timestamp = _timestamp.value;
			body.access_token = _access_token.value;
			body.appkey = _appkey.value;
			body.sign = _sign;
			console.log("=2=", body)
			return body;
		}
	},

	/**
	 * 序列化数据，处理JSON.stringify() 入参循环引用问题, 忽略循环引用属性
	 * @param param 需要序列化的数据
	 * @param spaces 指定缩进用的空白字符串
	 * @return {String} 序列化结果
	 */
	jkJsonStringify: function(param, spaces) {
		return JSON.stringify(param, serializer(), spaces);

		function serializer() {
			let stack = [];
			return function(key, value) {
				if (stack.length > 0) {
					let thisPos = stack.indexOf(this);
					~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
					if (~stack.indexOf(value)) {
						value = undefined; //忽略循环引用项
					}
				} else {
					stack.push(value);
				}
				return value;
			}
		}
	},

	// 判断是否是京东网页端
	jkInJDWeb: function() {
		var mainFramePage = topWindow.jkMainFramePage;
		if (mainFramePage.plate == 'jd_web') return true;
		return false;
	},

	//关闭窗口
	closeWindow: function(action) {
		// if (window.CloseOwnerWindow) return window.CloseOwnerWindow(action);
		// else window.close();
	},

	downloadFile: function(url, filename) {
		var a = document.createElement('a');
		a.href = url.replace(/^https?:/i, "");
		a.download = filename;
		a.click();
	},

	playSound: function(soundEnum) {
		var localConfigs = topWindow.localConfigs;
		if (!localConfigs) return;

		// 客户端总声音关闭，不播放声音
		if (localConfigs.voiceControl == 1) return;

		// if (typeof window.play_sound !== 'undefined') {
		// 	window.play_sound(soundEnum);
		// }
	},
	jkPlaySound: function(key) {
		var localConfigs = topWindow.localConfigs;
		if (!localConfigs) return;

		// 客户端总声音关闭，不播放声音
		if (localConfigs.voiceControl == 1) return;

		// 声音分类
		var voiceType = localConfigs.bussinessVoice;
		if (!voiceType) return;

		// 声音配置key
		var voiceConfig = voiceType.find(v => v.key == key);
		if (!voiceConfig) return;

		if (voiceConfig.state == 1) {
			// try {
			// 	window.jk_playSoundByPath(voiceConfig.path);
			// } catch (error) {}
		}
	},

	openPopPage: function(config) {
		// config = $.extend({
		// 	targetWindow: window,
		// 	url: '',
		// 	title: "",
		// 	width: "100%",
		// 	height: "100%",
		// 	showModel: true,
		// 	showHeader: true,
		// 	showCloseButton: true,
		// 	allowResize: false,
		// 	allowDrag: false,
		// 	onload: function() {},
		// 	ondestroy: function() {}
		// }, config);
		// return mini.open(config);
	},

	// 打开长链接
	openLongLink(longLink, shotLink) {
		if (!longLink || typeof longLink !== 'string') {
			return;
		}
		var moduleId,
			params = {},
			insideParams = jkUtils.getUrlParam(longLink, 'insideParams'),
			moduleParams = jkUtils.getUrlParam(longLink, 'moduleParams'),
			id = jkUtils.getUrlParam(longLink, 'id'),
			linkParams = insideParams || moduleParams,
			jkMainFramePage = topWindow.jkMainFramePage;

		if (!linkParams) {
			shotLink && jkUtils.openurl(shotLink);
			return
		}
		// 内部链接
		try {
			// base64解码，window.atob对中文参数解码有问题
			// linkParams = window.atob(linkParams).split("&");
			linkParams = topWindow.Base64.decode(linkParams).split("&");
			linkParams.forEach(p => {
				var a = p.split("=");
				params[a[0]] = a[1];
			});
		} catch (e) {}
		if (id) {
			try {
				var redirectParams = JSON.parse(params.redirectParams)
				redirectParams.id = id
				params.redirectParams = JSON.stringify(redirectParams)
			} catch (error) {}
		}
		// 内链
		if (insideParams) {
			moduleId = params.moduleId;
			if (moduleId === "doc_center" && topWindow.jkMainFramePage.plate === "jackyun_wdgj") {
				jkUtils.openurl(shotLink);
				return;
			}

			params = decodeURIComponent(params.params).split('&').map((p) => {
				return `\"${p.replace('=', '\":\"')}\"`;
			}).join(',');
			try {
				params = JSON.parse(`{${params}}`);
			} catch (e) {}

			if (!jkMainFramePage) return;
			// 多窗口打开短链接
			if (jkMainFramePage.isMultiWindow) {
				jkMainFramePage.openMainTab({
					type: "shotLink",
					moduleId: moduleId,
					params: params,
					isMultiWindow: true,
				});
				return;
			}

			var modules = jkMainFramePage.modules(),
				module = modules.find(m => m.moduleId === moduleId);

			if (!module) {
				jkUtils.showTip({
					content: '模块未找到，请与管理员联系！',
					state: 'warning',
				});
				return;
			}

			jkMainFramePage.openMainTab({
				type: "shotLink",
				url: module.moduleUrl,
				title: module.moduleName,
				moduleId: moduleId + "-" + Math.random().toString().split(".")[1], // 随机moduleId
				params: params,
			});
		}
		// 以消息的数据格式打开短链
		if (moduleParams) {
			if (!params.moduleId && params.redirectUrl) {
				if (params.redirectParams) {
					try {
						params.redirectParams = JSON.parse(params.redirectParams)
					} catch (error) {}
				}
				jkMainFramePage.openMainTab({
					type: "shotLink",
					url: params.redirectUrl,
					title: params.redirectTitle,
					moduleId: Math.random().toString().split(".")[1], // 随机moduleId
					params: params.redirectParams,
				});
				return
			}
			params.redirectMethod = "1";
			jkMainFramePage.openByMsg(params);
		}
	},

	// 打开内部外部短链接
	openLink: function(link, flag) {
		if (typeof link !== 'string') {
			jkUtils.showTip({
				content: '无效的链接地址！',
				state: 'warning'
			});
			return;
		}

		// 短链接
		if (/s.jkyun.biz\//i.test(link)) {
			if (link.indexOf('inside=') < 0) {
				// 新版本短链接，不带inside
				jkUtils.jkAjax({
					url: '/jkyun/basic-function/shortUrlToLong/getUnionLongUrl',
					data: {
						shortUrl: link
					}
				}).done(res => {
					res && jkUtils.openLongLink(res.data, link);
				});
				return;
			}

			// 旧版本短链接，带inside（以后可删除）
			var isInside = this.getUrlParam(link, 'inside');
			if (isInside == 0) {
				// 内部链接
				link = link.split('?')[0]; // 去除参数调接口
				jkUtils.jkAjax({
					url: '/jkyun/basic-function/shortUrlToLong/getLongUrl',
					data: {
						shortUrl: link
					}
				}).done(res => {
					var longLink = res.data,
						moduleId = jkUtils.getUrlParam(longLink, 'moduleId'),
						str = decodeURIComponent(jkUtils.getUrlParam(longLink, 'params'));

					if (moduleId === "doc_center" && topWindow.plate === "jackyun_wdgj") {
						jkUtils.openurl(link);
						return;
					}

					var params = str.split('&').map((p) => {
						return `\"${p.replace('=', '\":\"')}\"`;
					})
					// .join(',');
					try {
						params = JSON.parse(`{${params}}`);
					} catch (e) {

					}

					var jkMainFramePage = topWindow.jkMainFramePage;
					if (!jkMainFramePage) {
						return;
					}
					jkMainFramePage.openMenuFunc(moduleId, params, 'shotLink');
				});
				return;
			}
			if (flag === true) {
				// 控制首页搜索框不能打开 外部链接
				return;
			}
			// 外部链接
			jkUtils.openurl(link);
			return;
		}

		// 非短链接（其它链接）
		if (flag === true) {
			// 控制不能打开其它链接
			return;
		}
		jkUtils.openurl(link);
	},

	/**
	 *为miniUI下拉中出现表格的组件绑定表格拖拽改变宽度功能
	 *@param component {object} 需要绑定功能的miniUI组件对象
	 *@param callback {Function} 拖动改变后要执行的方法(可选)
	 */
	bindColumnResize: function(component, /*options*/ callback) {
		var that = component,
			//获取到下拉表格中的表头td元素
			$headerTD = $('td', $(that.listbox._headerEl)),
			//获取到下拉表格中的表头元素
			$header = $(that.listbox._headerEl),
			//定义显示拖拽宽度的阴影
			$shadowDiv,
			clientX,
			//用以记录阴影宽度
			shadowWidth,
			//记录是否能够有效拖拽
			vaildMouseDown = false,
			//记录当前组件的列配置
			componentColumns = that.columns,
			//记录被拖拽的列名
			resizeColumnsName;
		//为td绑定mousedown事件
		$headerTD.unbind('mousedown').on('mousedown', function(event) {
			//当鼠标按下时处于表格td右侧边缘10px以内时，表示进入允许拖拽区域
			if (event.offsetX > this.offsetWidth - 10) {
				//置有效拖拽位为true
				vaildMouseDown = true;
				//鼠标样式改为col-resize样式
				this.style.cursor = 'col-resize';
				//记录鼠标按下时的clientX
				clientX = event.clientX;
				//设置当前阴影宽度为offsetX
				shadowWidth = event.offsetX;
				//创建shadowDiv,其主要内容为样式设置
				var shadowDiv =
					`<div id = "td-resize-shadow" style = "position:absolute; width:${shadowWidth}px; top:0; bottom: 0; height:100%; background-color:rgba(0,0,0,0.3); z-index: 3;"></div>`;
				//将shadowDiv插入到td中
				$(this).append(shadowDiv);
				//获取到当前调节列的列名
				resizeColumnsName = $(this).text();
				//记录shadowDiv于$shadowDiv
				$shadowDiv = $('#td-resize-shadow', $(that.listbox._headerEl));
			}
		});

		//为当前td绑定mousemove事件，主要用于处理在td右侧边缘时鼠标样式的变化
		$headerTD.unbind('mousemove').on('mousemove', function(event) {
			//鼠标移动到边缘时修改鼠标样式为col-resize
			if (event.offsetX > this.offsetWidth - 10) {
				this.style.cursor = 'col-resize';
				//当鼠标非td边缘时且不处于拖动状态，修改鼠标样式为default
			} else if (vaildMouseDown == false) {
				this.style.cursor = 'default';
			}
		});

		//为整个表头元素绑定事件
		$header.unbind('mousemove').on('mousemove', function(event) {
			//元素处于可有效拖拽状态
			if (vaildMouseDown == true) {
				//阴影宽度等于本身宽度 + 当前点击事件clientX - 原本mousedown时事件的clientX值
				shadowWidth = shadowWidth + (event.clientX - clientX);
				//将当前clientX记录
				clientX = event.clientX;
				//设置阴影宽度为当前计算后的宽度
				$shadowDiv.width(shadowWidth);
			}
		});

		//为td绑定mouseup事件
		$headerTD.unbind('mouseup').on('mouseup', function() {
			//置有效拖拽状态为false
			vaildMouseDown = false;
			//读取当前表格列配置，找到调整过的列，将width更新为最新的宽度
			for (var i = 0; i < componentColumns.length; i++) {
				if (componentColumns[i].header == resizeColumnsName) {
					// huangjp 2020-02-06 解决设置的宽度比实际阴影宽度宽的问题
					componentColumns[i].width = shadowWidth - 17 + 'px';
					break;
				}
			}
			//将鼠标样式修正为default
			this.style.cursor = 'default';
			//移除shadow元素
			$('#td-resize-shadow').remove();
			//执行组件的setColumns方法，用最新的columns进行设置
			that.setColumns(componentColumns);
			// 保存列配置
			//获取当前列配置
			var nowColumns = this.columns;
			//调用配置保存接口，存储配置信息, 不提示保存成功
			this.saveName && jkUtils.jkSaveCCSConfig({
				actionName: this.saveName,
				configs: nowColumns,
			});
			if (typeof callback === "function") {
				callback.call(component);
			}
			//注意：setColumns会重新组建和渲染一次table,可以理解为重新创建了一个新的table,导致以上绑定事件全部无效,因此需要在拖动结束后重新执行
			//这里将整个过程事件绑定的形式呈现，因此重新fire一次bindcolumnsresize事件即可。
			jkUtils.bindColumnResize(that);
		});
	},

	// 显示提示
	defaultLayerCfg: {
		state: "success",
		mode: "normal",
		content: "",
		rsp: "",
		timeout: 3000,
		onload: null,
		callback: null,
	},
	layer: function(config) {
		config.title = config.title || "提示";
		// 初始化默认配置
		// config = window.mini && $.extend(mini.clone(jkUtils.defaultLayerCfg), config);
		// 气泡显示
		config.notice = function() {
			// 当前已显示的气泡提示
			var tips = [];
			$.each($('.mini-tips-success, .mini-tips-warning, .mini-tips-danger'), function(inex, item) {
				if ($(item).is(":visible")) tips.push(item);
			});

			// 如果是多个提示同时出现，则将之前的提示往上推
			tips.forEach(tip => {
				var targetMarginTop = parseFloat($(tip).css('margin-top')) - (parseFloat($(tip).css(
					'height')) < 19 ? 65 : 45);
				$(tip).animate({
					'margin-top': targetMarginTop + 'px'
				});
			});
			if (config.mode === "big") {
				config.state = `${config.state}-big`;
			}
			mini.showTips({
				target: config.target,
				state: config.state,
				content: config.content,
				timeout: config.timeout,
				x: "center",
				y: "cneter",
			});
			setTimeout(() => {
				mini.unmask();
				if (typeof config.callback === "function") {
					config.callback();
				}
			}, config.timeout);
		};
		// 提示框
		config.messageBox = function() {
			var iframe,
				data = {
					content: config.content,
					btns: config.btns,
					onload: config.onload,
				}
			if (Object.prototype.toString.call(config.rsp) === "[object Object]") {
				data.type = "interface";
				data.rsp = config.rsp;

				// 接口提示框只显示一个,普通提示框不受限制
				if (jkUtils._messageBox) {
					iframe = jkUtils._messageBox.getIFrameEl().contentWindow;
					if (typeof iframe.setData === "function") {
						data.firstLoad = false;
						iframe.setData(data);
						jkUtils._messageBox.set({
							title: config.title,
						});
						jkUtils._messageBox.show();
					}
					return;
				}
			}
			var messageBox = mini.open({
				// targetWindow: jkUtils.jkGetFitPopTargetWindow(window, 600, 500),
				cls: 'jk-message-box',
				url: '/common/message_box/messageBox.html',
				title: config.title,
				width: 500,
				height: 400,
				allowDrag: true,
				showHeader: true,
				showModel: true,
				allowResize: false,
				showCloseButton: true,
				maskOnLoad: false, // 不显示load效果
				onload: function() {
					var iframe = this.getIFrameEl().contentWindow;
					iframe && typeof iframe.setData === "function" && iframe.setData(data);
				},
				ondestroy: function() {
					messageBox.hide();
					if (typeof config.callback === "function") config.callback();
					return false;
				}
			});
			// 接口错误提示框只允许显示一个
			if (data.type === "interface") {
				jkUtils._messageBox = messageBox;
				return;
			}
		};

		// 提示框
		if (config.state === "error") {
			// 默认按钮
			if (!config.btns) {
				config.btns = [{
						id: "copy",
						type: "primary",
						text: "复制"
					},
					{
						id: "cancel",
						type: "default",
						text: "关闭"
					},
				];
			}
			if (Object.prototype.toString.call(config.rsp) === "[object Object]" && config.rsp.subCode.charAt(
					5) > 3) {
				config.btns.unshift({
					id: "submit",
					type: "primary",
					text: "一键提交工单",
					callback: null,
				});
			}

			mini.unmask();
			this.playAudio("fail");
			// 检测网络是否异常
			if (topWindow && topWindow.onlineFlag === false) {
				jkUtils.openOfflinePage();
				return;
			}
			config.messageBox();
			return;
		}
		// 绿色提示
		if (config.state === "success") {
			this.playAudio("success");
			config.timeout = 2000;
		} else {
			this.playAudio("fail");
		}
		// 黄色、红色提示
		config.notice();
	},

	openOfflinePage() { // 不提示断网弹框
		// if(topWindow.document.getElementById('offlineMsg') || document.getElementById('offlineMsg')) { // 多个弹框时仅显示一个断网提示
		//     return;
		// }
		// // var iframes = topWindow.frames;
		// // for (var i = 0; i < iframes.length; i++) {
		// //     if ($(iframes[i].document).find('#offlineMsg').length > 0) {
		// //         return;
		// //     }
		// // }
		// var isWin = topWindow.isMac;
		// var showCheckNetToolStr = `<div style="margin:10px 0 20px 77px">2、使用吉客云系统检查工具体检一下<span id="checkNetwork" style="color:#368BF0;padding-left:12px;cursor:pointer;">立即检测</span></div>`;
		// var showNumberSpanStr = `<span>1、</span>`;
		// var showCheckNetTool = isWin === "win" ? showCheckNetToolStr : "";
		// var showNumberSpan = isWin === "win" ? showNumberSpanStr : "";
		// var content = {
		//     title: '提醒',
		//     width: 500,
		//     height: 400,
		//     html: `
		//     <div id="offlineMsg" style="font-size:15px;">
		//         <div style="width:150px;height:150px;margin: 0 auto 10px">
		//             <img style="width: 150xp;height: 150px;" src="">
		//     </div>
		//     <div style="margin-left:20px">
		//         <div>可能原因：网络连接异常，防火墙或杀毒软件阻止等安全问题</div>
		//         <div style="margin-top:20px;">建议操作：${showNumberSpan}检查网络连接状况</div>
		//         ${showCheckNetTool}
		//     </div>
		// </div>`
		// }
		// if(topWindow.mini) {
		//     topWindow.mini.showMessageBox(content);
		// } else {
		//     mini.showMessageBox(content);
		// }

		// //断网立即检测工具
		// $("body").on("click", "#checkNetwork", function () {
		//     window.jk_openJackyunSystemRepair();
		// });
	},

	openSourceLoadErrorPage() {
		if (document.getElementById('offlineMsg')) { // 多个弹框时仅显示一个断网提示
			return;
		}

		var isWin = topWindow.isMac;
		var showToolBtn = isWin === "win" ? 'inline' : 'none';
		var content = {
			title: '提醒',
			width: 500,
			height: 400,
			html: `
            <div id="offlineMsg" style="font-size:15px;">
                <div style="width:150px;height:150px;margin: 0 auto 10px">
                    <img style="width: 150px; margin-top:23px;" src="">
            </div>
            <div style="margin:0 20px;">
                <div style = "text-align: center; font-size: 16px;color:red;">资源请求失败</div>
                <div style="margin-top:20px; margin-left: 120px; font-size: 14px;color:#000;">1.网络可能存在异常 <span id = "checkNetwork" style = "color: #368BF0; display:${showToolBtn}; cursor: pointer;">立即检测</span></div>
                <div style="margin-top:15px; margin-left: 120px; font-size: 14px;color:#000;">2.资源缓存异常 <span id = "clearCache" style = "color: #368BF0; display:${showToolBtn}; cursor: pointer;">立即清理</span></div>
            </div>
        </div>`
		}
		mini.showMessageBox(content);

		//断网立即检测工具
		$("body").on("click", "#checkNetwork", function() {
			// window.jk_openJackyunSystemRepair();
		});

		$("body").on("click", "#clearCache", function() {
			jkUtils.clearCacheConfirm();
		});
	},
	// 清除缓存
	clearCacheConfirm() {
		let isWin = topWindow.isMac;
		let winOSHtml = `<p style="font-size:14px;padding: 30px 0 0 16px;color:#000;">清除缓存需要重启客户端方可生效！</p>`;
		let winOSButtons = ['立即重启', '稍后重启', '取消'];
		let macOSButtons = ['确定', '取消'];
		mini.showMessageBox({
			width: 360,
			height: 200,
			title: '提示',
			html: winOSHtml,
			buttons: isWin === "win" ? winOSButtons : macOSButtons,
			callback: function(action) {
				if (action == '立即重启' || action == '确定') {
					try {
						if (topWindow.setLoginOutLog) {
							topWindow.setLoginOutLog();
						}
						// window.jk_cleancache(1);
					} catch (err) {

					}
				} else if (action == '稍后重启') {
					try {
						// window.jk_cleancache(2);
					} catch (err) {

					}
				}
			}
		});
	},

	showMessageBox: function(title, config, callback) {
		if (!config.rsp) { // 本地错误
			var data = {
				message: config.content,
				errorCode: config.errorCode,
				callback: callback
			};
			var errorinfo = config.handlerInfo;
			if (errorinfo) {
				data.handleType = errorinfo.handlerType;
				data.handlerUrl = errorinfo.handlerUrl;
				data.handlerParam = errorinfo.handlerParam;
			}
			if (typeof config === "string") {
				data = {
					message: config
				}
			}
		} else { // 服务器错误
			var data = {
				message: config.rsp.msg,
				errorCode: config.rsp.result.contextId,
				subCode: config.subCode,
				callback: callback
			};
			if (config.rsp.code !== 452) { // 需要显示提交错误工单的错误
				data.url = config.rsp.url;
				data.dataString = config.rsp.dataString;
			}
			var errorinfo = config.rsp.result.handlerInfo;
			if (errorinfo) {
				data.handleType = errorinfo.handlerType;
				data.handlerUrl = errorinfo.handlerUrl;
				data.handlerParam = errorinfo.handlerParam;
			}
		}

		if (jkUtils._jkMessageBox) {
			setTimeout(function() {
				var jkMessageBox = jkUtils._jkMessageBox.getIFrameEl().contentWindow.jkMessageBox;
				if (jkMessageBox) jkMessageBox.setData(data);
			}, 500);
			return;
		}

		jkUtils._jkMessageBox = mini.open({
			// targetWindow: jkUtils.jkGetFitPopTargetWindow(window, 600, 500),
			url: '/common/jk_com_page/jk_msg_box/jk_msg_box.html',
			cls: 'jk-message-box',
			title: title,
			width: 500,
			height: 400,
			allowDrag: true,
			showHeader: true,
			showModel: true,
			allowResize: false,
			showCloseButton: true,
			maskOnLoad: false, // 不显示load效果
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.jkMessageBox.setData(data);
			},
			ondestroy: function() {
				jkUtils._jkMessageBox = null;
				if (typeof config.callback == 'function') config.callback();
			}
		});
	},

	//需用户确认的提示框
	showErrMessageBox: function(config, callback) {
		this.showMessageBox(jkUtils.jki18n('com_tip', '提示', 'common'), config, callback);
	},

	showTipDefaultCfg: {
		content: '',
		state: 'success',
		timeout: 3000,
		x: 'center',
		y: 'center',
		rsp: null, // 请求的返回对象，用于方便后端查询问题
		mode: 'normal', // 显示的大小模式，normal是普通，big是放大模式，放大模式不显示contextId
		zindex: 99999999 //层级
	},

	// 显示提示信息
	// message: 要显示的消息
	// callback: 回调函数
	// timeout: 显示时常
	// state: 要显示的信息样式，具体参考mini ui
	// posX: 水平位置
	// posY: 垂直位置
	_showContextId: function(config, rsp) {
		var text = ' ' + jkUtils.jki18n('base_subcode', '错误代码', 'base') + ': ';
		if (config.state === 'danger')
			this.writeLogToShell(config.content + text + rsp.result.contextId);
		if (jkUtils.isInDev())
			config.content += (((config.mode !== 'big')) ? (text + rsp.result.contextId) : '');
		else
			config.content += (config.state === 'danger' ? (text + rsp.result.contextId) : '');
	},

	showTip: function(params, callback) {
		// 成功警告提示转layer，集成屏蔽danger和ajax的提示
		var rsp = params.rsp;
		if (jkUtils.isInDev()) {
			if (params.state != "danger" && !rsp) {
				params.callback = callback;
				jkUtils.layer(params)
			}
			return;
		}

		function popTips() {
			// 如果是多个提示同时出现，则将之前的提示往上推
			var $existTips = [];
			$.each($('.mini-tips-success, .mini-tips-warning'), function(index, item) {
				if ($(item).is(':visible'))
					$existTips.push(item);
			});
			var msgArr = $existTips.map(function(tip) {
				return $(tip).text();
			});
			var currMsg = params.rsp ? params.rsp.msg : params.content;
			var isMsgExist = msgArr.length > 0 &&
				$.grep(msgArr, function(item) {
					return item && currMsg && item.split(' ' + jkUtils.jki18n('base_subcode', '错误代码',
						'base') + ': ')[0] === currMsg;
				}).length > 0;
			if ($existTips.length > 0 && !isMsgExist) {
				$existTips.forEach(function(tip) {
					var targetMarginTop = parseFloat($(tip).css('margin-top')) - (parseFloat($(tip).css(
						'height')) < 19 ? 65 : 45);
					$(tip).animate({
						'margin-top': targetMarginTop + 'px'
					});
				});
			}

			if (!isMsgExist) {
				mini.showTips(config);
				setTimeout(function() {
					if (callback)
						callback();
					deferred.resolve();
					mini.unmask();
				}, config.timeout);
			}
		}

		var deferred = $.Deferred();
		// var config = window.mini && $.extend(mini.clone(this.showTipDefaultCfg), params);

		if (rsp) {
			var code = rsp.code;

			if (code == jkEnums.CommonRspCode.Success)
				config.state = (config.mode === 'big' ? 'success-big' : 'success');
			else {
				config.subCode = rsp.subCode;
				if (!config.subCode && rsp.msg === '未知错误') {
					config.state = 'danger';
				} else {
					var sc = config.subCode.split('');
					if (sc[5] * 1 > 3) {
						config.state = 'danger';
					} else if (sc[5] * 1 == 3) {
						config.state = 'danger';
						delete config['rsp']['url'];
					} else {
						config.state = (config.mode === 'big' ? 'warning-big' : 'warning');
					}
				}

				config.timeout = 5000;
			}
			config.content = config.rsp.msg;
			this._showContextId(config, rsp);
		}

		if (config.state.indexOf('success') >= 0) {
			config.timeout = 2000;
			this.playAudio("success");
			popTips();
		} else if (config.state === 'danger') {
			mini.unmask();
			this.playAudio("fail");
			this.showErrMessageBox(config, function() {
				if (callback) callback();
				deferred.resolve();
			});
		} else {
			this.playAudio("fail");
			popTips();
		}
		return deferred;
	},
	showTip2: function(params) {
		var config = $.extend(mini.clone(this.showTipDefaultCfg), params);
		if (config.rsp)
			config.content = config.rsp.msg;

		if (config.rsp)
			this.writeLogToShell(config.content + ' contextId:' + config.rsp.result.contextId);

		// if (window.jk_debuger && config.rsp)
		// 	config.content += ' contextId:' + config.rsp.result.contextId;

		mini.unmask();
		if (config.state === 'success')
			this.playAudio("success");
		else
			this.playAudio("fail");
		mini.showTips(config);
		return $.Deferred(function(deferred) {
			setTimeout(function() {
				deferred.resolve();
			}, config.timeout);
		}).promise();
	},

	// rspData: Ajax返回数据
	// sucFunc: 成功时调用的方法
	// failFunc: 失败时调用的方法
	// isSucCustom: 成功时，是否放弃通用处理逻辑
	// isFailCustom: 失败时，是否放弃通用处理逻辑
	handleAjaxRsp: function(rspData, sucFunc, failFunc, isSucCustom, isFailCustom) {
		if (rspData.code == jkEnums.CommonRspCode.Success) {
			if (sucFunc && isSucCustom)
				sucFunc(rspData);
			else
				jkUtils.showTip({
					content: rspData.msg
				}, function() {
					if (sucFunc)
						sucFunc(rspData);
				});
		} else {
			if ($.inArray(rspData.code, [401, 402]) !== -1) {
				mini.alert(jkUtils.jki18n('base_msg_100001', '登录超时，请重新登录！', 'base'), jkUtils.jki18n('com_tip',
					'提示', 'common'), function() {
					// if (typeof window.main_control !== 'undefined')
					// 	window.main_control(3);
				});
			} else {
				if (failFunc && isFailCustom)
					failFunc(rspData);
				else
					this.showTip({
						rsp: rspData,
						state: 'danger'
					}, function() {
						if (failFunc)
							failFunc(rspData);
					});
			}
		}
	},

	// 当请求出错的时候，进行统一的操作
	handleAjaxFailed: function(rsp) {
		if ($.inArray(rsp.code, [401, 402]) != -1) {
			mini.alert(jkUtils.jki18n('base_msg_100001', '登录超时，请重新登录！', 'base'), jkUtils.jki18n('com_tip', '提示',
				'common'), function() {
				// if (typeof window.main_control !== 'undefined')
				// 	window.main_control(3);
			});
		} else
			this.showTip({
				rsp: rsp,
				state: 'danger'
			});
	},

	// 当Ajax请求出错的时候，用来提示错误信息
	showAjaxRequestError: function(error) {
		if (error && error.responseText) {
			var obj = JSON.parse(error.responseText);
			this.showTip({
				content: obj.message,
				state: 'danger'
			});
		}
	},

	//将数组拼接成字符串
	// arr: 要拼接的数组
	// separator: 分隔符
	// defVal: 默认值
	arrayToStr(arr, separator, defVal) {
		var result = '';
		if (defVal) result = defVal;
		if (arr && Object.prototype.toString.call(arr) == '[object Array]')
			result = $.grep(arr, function(value) {
				return value !== undefined && value !== '' && value !== null;
			}).join(separator);

		return result;
	},

	// 数组排重（只针对一维数组）
	unique(arr) {
		var res = [];
		var json = {};
		for (var i = 0; i < arr.length; i++) {
			if (!json[arr[i]]) {
				res.push(arr[i]);
				json[arr[i]] = 1;
			}
		}
		return res;
	},

	// 所有界面的输入弹出框
	inputFrame: null,

	// 校验方法，比较两个控件的大小值
	onGtThanValidate: function(e, ctrlId, type, errorText) {
		if (e.isValid) {
			var ctrl = mini.get(ctrlId);
			if (ctrl.isValid() && ctrl.getValue() && e.value) {
				var isValid = true;
				switch (type) {
					case 'int':
						isValid = parseInt(e.value) > parseInt(ctrl.getValue());
						break;
					default:
						isValid = e.value > ctrl.getValue();
						break;
				}
				if (!isValid) {
					e.isValid = false;
					e.errorText = errorText;
				}
			}
		}
	},

	// 根据校验方法
	//对象结构 {"member_name":"jackyun_dev","user_name":"jackyun_dev","system_ip":"http://192.168.88.51:80","company":"鍚夊浜戝紑鍙?","member_id":"45","user_id":"459215893833718912"}
	getLoginData: function() {
		return jkUtils.getLoginInfo();
	},

	//获取用户信息
	// parmasList:string 需要用到的参数列表，如果没有传入，则默认获取所有参数
	getUserInfo: function(parmasList, type = 'get') {
		return jkUtils.jkAjax({
			url: '/jkyun/erp-baseinfo/open/user/getbyuserid',
			type,
			data: {
				fieldNames: parmasList
			}
		});
	},

	getUserInfo2: function() {
		var loginInfo = jkUtils.getLoginInfo();
		return jkUtils.jkAjax({
			url: '/jkyun/erp-baseinfo/user/getbyid',
			type: 'get',
			data: {
				userId: loginInfo.user_id
			}
		});
	},

	// 获取客户经理信息
	getServerInfo: function() {
		return topWindow.serverInfo || {}
	},

	//mini.mask参数一致
	jkDoWaitting: function(jkAjax, options, callback) {
		options = $.extend({
			el: document.body,
			cls: 'mini-mask-loading',
			html: jkUtils.jki18n('base_msg_100000', '正在加载...', 'base')
		}, options);

		mini.jkmask(options);
		jkAjax.always(function() {
			mini.unmask(options.el);
			if (callback)
				callback();
		});
		return jkAjax;
	},

	jkDefaultAjaxOptions: {
		useDataFilter: true, // 是否启用架构层接口数据过滤器
		useErrorHandler: true, // 是否启用架构层接口错误拦截器
		// 接口没调通的错误 (404 500 502 504)
		netError: function(error, callback) { // options是ajax接口请求参数对象
			// return;
			error.subCode = "";
			error.contextId = error.status;
			jkUtils.layer({
				state: "error",
				content: error.content,
				rsp: error,
				callback: callback,
			});
		},
		// 接口调通，后端统一拦截的错误 (452 453)
		serverError: function(error, callback) { // options是ajax接口请求参数对象
			if (!error.errorType) return;
			// 数据库分配验证
			if (error.subCode === '0196680000' || error.subCode === '0196680001' || error.subCode ===
				'0196680002' || error.subCode === '0196680003') {
				if (!databaseIntercept) {
					databaseIntercept = jkUtils.openPopPage({
						// targetWindow: window,
						url: "/common/databaseInit/mrpPage.html",
						title: "",
						width: 940,
						height: topWindow.innerHeight < 980 ? 590 : 900,
						showHeader: false,
						showCloseButton: false,
						onload: function() {
							// 防止被遮罩
							$('.mini-mask.mini-fixed').hide();
							var iframe = this.getIFrameEl().contentWindow;
							iframe.setData({
								subcode: error.subCode,
								popWindow: databaseIntercept
							});
						}
					})
				}
				// 数据库资源未初始化时接口没有错误弹框
				return;
			}
			if (error.errorType === "business") {
				var handlerInfo = error.result.handlerInfo;
				if (handlerInfo && handlerInfo.handlerType == 100) {
					try {
						var skipInfo = JSON.parse(handlerInfo.handlerUrl);
						error.msg =
							`${error.msg}<br /><span class="evt-skip" type="tab" moduleId="${skipInfo.moduleId}" params='${handlerInfo.handlerParam}'>点击前往 ${skipInfo.moduleName}</span>`;
					} catch (e) {}
				}
			}

			var subCode = error.subCode ? error.subCode : "";
			if (subCode.charAt(5) >= 3) {
				error.contextId = error.result.contextId;
				jkUtils.layer({
					state: "error",
					title: error.messageBoxTitle,
					content: error.msg,
					rsp: error,
					callback: callback,
				});
			} else {
				jkUtils.layer({
					state: "warning",
					content: error.msg,
					callback: callback,
				});
			}
		},
		// 401时提示气泡
		loginOut: function() {
			jkUtils.layer({
				state: "warning",
				content: "登录超时，请重新登录！",
				callback: function() {
					topWindow.loginOut();
				}
			});
		},
		serverDataFilter: function(data) {
			if (data.code == jkEnums.CommonRspCode.Success) {
				return $.Deferred().resolveWith(this, [data.result, data.msg, data.code, data]);
			} else {
				return $.Deferred().reject(data);
			}
		},
		networkErrorHandler: function(jqXHR, textStatus, errorThrown) {
			var content = jkUtils.jki18n('base_msg_100003', '请求失败', 'base');
			content += ' ' + jkUtils.jki18n('base_subcode', '错误代码', 'base') + ': ';
			content += jqXHR.status;
			content += ' ' + jkUtils.jki18n('base_msg_100042', '错误信息', 'base');
			content += errorThrown;

			jkUtils.showTip({
				content: content,
				errorCode: jqXHR.status,
				state: 'danger'
			});
		},
		serverErrorHandler: function(error, options, callback) {
			if (typeof options === 'object') {
				error.url = options.url;
				error.dataString = options.dataString;
			} else if (typeof options === 'function') { // 兼容一下以前第二个参数传的是回调函数
				callback = options;
			}
			var handlerInfo = error.result.handlerInfo;
			if (handlerInfo) {
				if (error.result.handlerInfo.handlerType != 200) jkUtils.showTip({
					rsp: error,
					state: 'danger',
					callback: callback
				});
			} else {
				jkUtils.showTip({
					rsp: error,
					state: 'danger',
					callback: callback
				});
			}
		},
		// 新的jkAjax方法统一处理接口状态码401、403、452错误，404、500、504错误统一抛出错误给业务层，不做特殊处理
		errorHandler: {
			// 请求接口时网络异常
			"0": function() {
				// if (!window.navigator.onLine || window.navigator.connection) {
				// 	jkUtils.offlineErrorLayer();
				// }
			},
			"403": function() {
				jkUtils.showTip({
					content: '请求受限！',
					state: 'warning'
				});
			},
			// 网关限流
			"452": function(error) {
				jkUtils.showTip({
					state: 'danger',
					content: error.msg,
					errorCode: error.subCode,
				});
			},
			// 网关隔离
			"453": function(error) {
				jkUtils.showTip({
					state: 'danger',
					content: error.msg,
					errorCode: error.subCode,
				});
			},
			// 404 及服务器 500 502 504 等错误
			"default": function(err, status, msg) {
				jkUtils.showTip({
					content: "请求失败：" + msg,
					errorCode: err.status,
					state: "danger",
				});
			},
		},
	},
	jkAjax: function() {
		/**
		 * arguments 入参说明
		 * @param arguments[0]  object 为接口入参选项 (取消了第一个参数为接口地址的方式，统一传入参数对象)
		 * @param arguments[...]  待定，扩展业务组特殊入参
		 **/
		const deferreds = $.Deferred();

		// 数据库资源没有初始化时，接口拦截
		if (databaseIntercept) return deferreds.reject('数据库资源未初始化');


		// 需要注意一下arguments的作用域
		var params = arguments[0];
		if (Object.prototype.toString.call(params) !== "[object Object]") {
			console.warn('请按要求入参');
			return $.Deferred();
		}

		// 接口请求配置对象（不限制参数传的字符串）
		if (!params.data) {
			// 容错，格式化后端接口入参对象
			params.data = {}
		}

		if (params.isLocalFile) params.headers ? params.headers['NotRepost'] = '*' : params.headers = {
			'NotRepost': '*'
		};
		//接口请求中发送
		if (!params.timeout) params.timeout = 60000;

		// 获取二次验证所需的验证码，并存入接口入参对象
		var commonVerify = topWindow.sessionStorage.getItem('commonVerify');
		if (commonVerify) {
			params.data.commonVerify = commonVerify;
		}

		// 授权验证token
		if (params.authVerify) {
			params.data.authVerify = params.authVerify;
			delete params.authority;
		}

		if (topWindow && topWindow.onlineFlag === false && params.unTestNet) {
			return deferreds.reject('网络异常');
		}
		// 储存接口请求对象，方便二次验证第二次请求使用
		var options = $.extend({}, this.jkDefaultAjaxOptions, params);
		// 计算签名可配，默认架构计算签名，有特殊业务（excel文件上传）需要特殊处理签名
		// 不建议设置 isGetSign=false（设置之后不支持二次验证和web版无痛刷新token功能）
		var isForceJackyun = false;
		if (jkUtils.isJkyun_J() && jkUtils.isPddRequest(params.url)) {
			isForceJackyun = true;
		}
		// 吉客云J系列增加新的参数, 判断如果是吉客云J系列, 并且请求拼多多相关接口, 则使用拼多多相关签名计算
		if (options.isGetSign !== false) {
			params.data = this.jkGetSign(params.data, undefined, undefined, isForceJackyun);
		}
		options.jqXHR = null;
		options.dataString = params.data;
		var ajaxBeginTime = (new Date()).valueOf();
		//记录ajax请求开始的时间
		options.ajaxBeginTime = ajaxBeginTime;
		// 需要将ajax请求赋值给一个变量并返回，业务组处理结果的回调中才会有重组过后的参数
		var ajax = $.ajax(params);
		var jkAjax = ajax.then(function() {
			// arguments = [res, state, jqXHR]
			var args = arguments,
				res = args[0],
				deferred = $.Deferred();
			//将jQuery的ajax对象引入到options中, 以便.always中进行统一处理时能够获取
			if (options.jqXHR === null) {
				options.jqXHR = args[2];
			}
			// 处理请求静态json文件，比如menuList.json
			if ($.isArray(res)) {
				return deferred.resolveWith(this, args);
			}

			//需要弹出重启客户端并清理缓存的弹窗, 注意: 以33099结尾的subCode会触发该逻辑
			if (typeof res.subCode === "string" && res.subCode.endsWith("33099")) {
				let config = {
					id: "restart-tip",
					title: "系统更新提醒",
					width: 400,
					buttons: ["立即重启", "稍后重启", "取消"],
					html: `<div style="height: 60px;
                    text-align: center;
                    font-size: 14px;
                    padding-top: 30px;
                    color: #333333">
                                ${res.msg}
                        </div>`,
					callback: function(action) {
						switch (action) {
							case "立即重启":
								try {
									if (topWindow.setLoginOutLog) {
										topWindow.setLoginOutLog();
									}
									// window.jk_cleancache(1);
								} catch (e) {}
								break;
							case "稍后重启":
								try {
									// window.jk_cleancache(2);
								} catch (e) {}
								break;
							default:
						}
					}
				}
				mini.showMessageBox(config);
				let errors = [args[2], args[1], res];
				return deferred.rejectWith(this, errors);
			}

			// 数据库分配验证
			if (res.subCode === '0196680000' || res.subCode === '0196680001' || res.subCode ===
				'0196680002' || res.subCode === '0196680003') {
				if (!databaseIntercept) {
					databaseIntercept = jkUtils.openPopPage({
						// targetWindow: window,
						url: "/common/databaseInit/mrpPage.html",
						title: "",
						width: 940,
						height: topWindow.innerHeight < 980 ? 590 : 900,
						showHeader: false,
						showCloseButton: false,
						onload: function() {
							// 防止被遮罩
							$('.mini-mask.mini-fixed').hide();
							var iframe = this.getIFrameEl().contentWindow;
							iframe.setData({
								subcode: res.subCode,
								popWindow: databaseIntercept
							});
						}
					})
				}
				// 数据库资源未初始化时接口没有错误弹框
				return deferred.rejectWith(this, args);
			}

			// code != 200 返回错误结果，会跳到下面的fail
			if (res.code != jkEnums.CommonRspCode.Success) {
				return deferred.reject(res);
			}

			// 接口二次验证，这里 reject 要重组reject参数
			if (res.subCode === "0190210000" && options.isGetSign !== false) {
				var _this = this,
					data = res.result.data,
					type = data.validateType,
					popwidth = 400,
					popheight = (type === "Face") ? 410 : 295,
					errors = [args[2], args[1], res];


				if (!type) {
					return deferred.rejectWith(_this, errors);
				}

				// 打开验证弹窗
				var commonVerify = jkUtils.openPopPage({
					// targetWindow: jkUtils.jkGetFitPopTargetWindow(window, 600, 500),
					url: "/system/security_recognation.html",
					title: "二次验证",
					width: popwidth,
					height: popheight,
					showHeader: false,
					showCloseButton: false,
					onload: function() {
						var iframe = this.getIFrameEl().contentWindow;
						iframe.setData({
							type: type,
							popWindow: commonVerify,
							validateData: data
						});

						// 防止被遮罩
						$('.mini-mask.mini-fixed').hide();
					},
					ondestroy: function(action) {
						if (action !== 'ok') {
							// 取消验证窗口返回错误给业务组
							return deferred.rejectWith(_this, errors);
						}

						var iframe = this.getIFrameEl().contentWindow,
							token = iframe.getData().token;

						// 获取到了验证token，存入sessionStorage，调用其它接口自动会加入此token入参
						token = token ? token : "";
						topWindow.sessionStorage.setItem('commonVerify', token);

						// 重新调用接口
						jkUtils.jkAjax(options).done(function() {
							// 将第二次调用接口的结果通过新的异步对象返回给业务组
							deferred.resolveWith(this, arguments);
						}).fail(function() {
							deferred.rejectWith(this, arguments);
						});
					}
				});
				// 返回新的异步对象，拦截ajax.done
				return deferred;
			}

			// 订单解密风控验证
			if (res.subCode === "0031117002" && options.isGetSign !== false) {
				const result = res.result || {};
				const data = result.data || {};
				const verifyId = data.verifyId;
				const base64 = data.base64;
				const environment = data.environment;
				// 参数存在的情况下开启校验
				if (verifyId && base64) {
					const _this = this;
					const errors = [args[2], args[1], res];

					// 打开验证弹窗
					const riskVerify = jkUtils.openPopPage({
						// targetWindow: jkUtils.jkGetFitPopTargetWindow(window, 600, 500),
						url: "/system/build/risk_verification/index.html",
						title: "风控验证",
						width: 400,
						height: 230,
						onload: function() {
							const iframe = this.getIFrameEl().contentWindow;
							const setData = iframe && iframe.setData;
							typeof setData === 'function' && setData({
								popWindow: riskVerify,
								base64,
								verifyId,
								environment,
							});

							// 防止被遮罩
							$('.mini-mask.mini-fixed').hide();
						},
						ondestroy: function(action) {
							// 风险验证通过，再次请求原先接口
							if (action === 'success') {
								// 重新调用接口
								return jkUtils.jkAjax(options).done(function() {
									// 将第二次调用接口的结果通过新的异步对象返回给业务组
									deferred.resolveWith(this, arguments);
								}).fail(function() {
									deferred.rejectWith(this, arguments);
								});
							}
							return deferred.rejectWith(_this, errors);
						}
					});
					// 返回新的异步对象，拦截ajax.done
					return deferred;
				}
			}

			// 授权验证
			if (res.subCode === "0190210006" && options.isGetSign !== false) {
				let _this = this,
					data = res.result.data,
					type = data.validateType,
					popwidth = 400,
					popheight = (type === "Face") ? 410 : 295,
					errors = [args[2], args[1], res];

				if (!type) {
					return deferred.rejectWith(_this, errors);
				}

				// 打开验证弹窗
				const authVerify = jkUtils.openPopPage({
					// targetWindow: jkUtils.jkGetFitPopTargetWindow(window, 600, 500),
					url: "/system/security_recognation.html",
					title: "授权验证",
					width: popwidth,
					height: popheight,
					showHeader: false,
					showCloseButton: false,
					onload: function() {
						const iframe = this.getIFrameEl().contentWindow;
						iframe.setData({
							type: type,
							validateData: data,
							popWindow: authVerify
						});

						// 防止被遮罩
						$('.mini-mask.mini-fixed').hide();
					},
					ondestroy: function(action) {
						if (action !== 'ok') {
							// 取消验证窗口返回错误给业务组
							return deferred.rejectWith(_this, errors);
						}

						let
							iframe = this.getIFrameEl().contentWindow,
							token = iframe.getData().token;

						// 获取到了验证token，存入sessionStorage，调用其它接口自动会加入此token入参
						token = token ? token : "";
						options.authVerify = token;
						// 重新调用接口
						jkUtils.jkAjax(options).done(function() {
							// 将第二次调用接口的结果通过新的异步对象返回给业务组
							deferred.resolveWith(this, arguments);
						}).fail(function() {
							deferred.rejectWith(this, arguments);
						});
					}
				});
				// 返回新的异步对象，拦截ajax.done
				return deferred;
			}

			// 返回结果，useDataFilter 表示是否开启对返回的数据重组，
			if (options.useDataFilter) {
				// 重组数据，返回(res.result, res.msg, res.code, res)
				res.result.subCode = res.subCode;
				return deferred.resolve(res.result, res.msg, res.code, res);
			}
			// 返回 (res, jqXHR.msg, jkXHR)
			return deferred.resolveWith(this, args);
		}).then(null, function() {
			// 第一个参数为null的.then回调，是处理错误的请求，可以拦截ajax.fail
			var args = arguments,
				deferred = $.Deferred(),
				defaultAjaxOptions = jkUtils.jkDefaultAjaxOptions;
			//将jQuery的ajax对象引入到options中, 以便.always中进行统一处理时能够获取
			if (options.jqXHR === null) {
				options.jqXHR = args[0];
			}
			if (options.forceUseErrorHandler === false) return deferred.rejectWith(this, args);
			// 接口请求通过，但 res.code!=200 的错误，// arguments = [error];

			// wms重试需求，responseText为空并且接口超时
			if (options.shouldRetry) {
				options.retryTimes = options.retryTimes || 0;
				if (options.jqXHR.responseText === void 0) {
					options.retryTimes++;
					if (options.retryTimes <= 3) {
						setTimeout(() => {
							jkUtils.jkAjax(options).done(function() {
								deferred.resolve(arguments[0]);
							}).fail(function() {
								deferred.reject(arguments[0]);
							})
						}, 1050);
						return deferred;
					}
				}
			}

			if (args.length === 1) {
				options.errorRetryTimes = options.errorRetryTimes || 0;
				if (args[0].subCode === '0190210007') {
					options.errorRetryTimes++
					if (options.errorRetryTimes <= 3) {
						setTimeout(() => {
							jkUtils.jkAjax(options).done(function() {
								deferred.resolve(arguments[0]);
							}).fail(function() {
								deferred.reject(arguments[0]);
							})
						}, 1050);
						return deferred
					}
				}
				// 0190210007重试超过3失败则提示服务繁忙
				if (options.errorRetryTimes > 3) {
					args[0].msg = '系统繁忙，请稍后重试';
				}
				// 配置了 useErrorHandler=true 使用架构统一错误处理
				args[0].errorType = "business";
				args[0].url = options.url;
				args[0].dataString = options.dataString;
				args[0].messageBoxTitle = options.messageBoxTitle;
				if (options.useErrorHandler) {
					defaultAjaxOptions.serverError(args[0]);
				}
				return deferred.rejectWith(this, args);
			}

			// 统一处理错误 arguments = [jqXHR, state, msg];
			var jqXHR = args[0],
				status = jqXHR.status && jqXHR.status.toString(),
				error = jkUtils.jkParseJson(jqXHR.responseText);
			// 二次验证如果不验证会到这里
			if (status == 200) {
				return deferred.reject(error);
			}
			if (args[2] === "abort") {
				return deferred.rejectWith(this, args);
			}
			// 处理接口超时,接口超时status=0
			if (args[1] === "timeout") {
				mini.showTips({
					content: "请求超时, 请稍后重试, 接口地址: " + options.url,
					state: "warning",
					y: 'center',
					timeout: 4000
				});
				args[0].abort();
				return deferred.reject([jqXHR, status, error]);
			}
			// 不明原因的错误，像接口取消之类的
			if (status == 0) {
				// 断网处理
				// if (!window.navigator.onLine) {
				// 	jkUtils.offlineErrorLayer();
				// }
				return deferred.reject(error);
			}
			// 处理502 504接口重试
			options.retryTimes = options.retryTimes || 0;
			if (status == 502 || status == 504) {
				options.retryTimes++
				if (options.retryTimes <= 2) {
					setTimeout(() => {
						jkUtils.jkAjax(options).done(function() {
							deferred.resolve(arguments[0]);
						}).fail(function() {
							deferred.reject(arguments[0]);
						})
					}, 1050);
					return deferred
				}
			}
			// 统一处理404 500等错误
			if (Object.prototype.toString.call(error) !== "[object Object]") {
				if (!options.forceHideErrorFrame) {
					defaultAjaxOptions.netError({
						url: options.url,
						content: `请求失败: ${jqXHR.statusText}`,
						status: jqXHR.status,
					});
				}
				return deferred.rejectWith(this, args);
			}
			// error信息没有result也是404 500等错误
			if (!error.result) {
				if (!options.forceHideErrorFrame) {
					error.url = error.path;
					error.content = `请求失败: ${error.error}, ${error.message}`;
					defaultAjaxOptions.netError(error);
				}
				return deferred.reject(error);
			}

			// 处理 401 错误
			if (status == 401) {
				// subCode != "0190210003" 给黄色提示
				if (error.subCode !== "0190210003") {
					jkUtils.layer({
						state: 'warning',
						content: error.msg,
					});
					return deferred.reject(error);
				}
				// 客户端会自动更新token，如果token过期 => 锁屏
				if (jkUtils.jkInCEF()) {
					// 在客户端
					if (topWindow.isLocked) {
						return deferred.reject(error);
					}
					// 清除自动锁屏的定时器
					clearTimeout(topWindow.lockTimer);
					topWindow.isLocked = true;

					jkUtils.layer({
						state: 'warning',
						content: jkUtils.jki18n('base_msg_100001', '登录超时，请重新登录！', 'base'),
						callback: function() {
							topWindow.invalidLockedObject.isTokenInvalidLocked = true;
							topWindow.lock();
						}
					});
					return deferred.reject(error);
				}
				// 在京东或阿里环境
				if (["jackyun_ali", "jd_web"].indexOf(topWindow.jkMainFramePage.plate) >= 0) {
					jkUtils.layer({
						state: 'warning',
						content: jkUtils.jki18n('base_msg_100001', '登录超时，请重新登录！', 'base'),
					});
					return deferred.reject(error);
				}
				// web版token过期提示
				var refreshToken = localStorage.getItem("refresh_token");
				if (!refreshToken || refreshToken === "undefined" || refreshToken === "null" || options
					.isGetSign === false) {
					defaultAjaxOptions.loginOut();
					return deferred.reject(error);
				}

				// 自动更新token
				$.ajax({
					url: '/auth/refresh',
					type: 'post',
					headers: {
						'clientId': topWindow.appKey
					},
					data: {
						refreshToken: refreshToken
					},
				}).done(res => {
					if (!res.access_token || !res.refresh_token) {
						deferred.reject(error);
						defaultAjaxOptions.loginOut();
						return;
					}

					var loginInfo = topWindow.loginInfo;
					jkUtils.cookie.setCookie('token', res.access_token, '', 'basePath');
					localStorage.setItem("refresh_token", res.refresh_token);
					// 同时更新一下用户信息，防止用户信息过期
					for (let key in loginInfo) {
						key !== "loginResult" ? localStorage.setItem(key, loginInfo[key]) :
							null;
					}

					jkUtils.jkAjax(options).done(function() {
						// 第二次请求的结果返回给deferred
						deferred.resolveWith(this, arguments);
					}).fail(function() {
						// 第二次请求的错误返回给deferred
						deferred.resolveWith(this, arguments);
					});
				}).fail(() => {
					defaultAjaxOptions.loginOut();
					deferred.reject(error);
				});
				return deferred;
			}

			// 接口调通，后端统一拦截的错误
			error.url = options.url;
			error.dataString = options.dataString;
			error.errorType = "intercept";
			defaultAjaxOptions.serverError(error);
			return deferred.reject(error);
		}).always(function() {
			var ajaxEndTime = (new Date()).valueOf();
			//记录ajax请求结束的时间
			options.ajaxEndTime = ajaxEndTime;
			//本次ajax返回所用的绝对时间(单位:秒)
			var absoluteTime = options.ajaxEndTime - options.ajaxBeginTime;
			//进入记录ajax的web日志的流程
			const errorRetryTimes = options.errorRetryTimes;
			if (options.writeAjaxWeblog === false || ~[1, 2, 3].indexOf(errorRetryTimes)) return;
			jkUtils.writeAjaxWeblog(options, absoluteTime);
		});
		jkAjax.abort = function() {
			return ajax.abort.apply(ajax, arguments);
		}

		return jkAjax;
	},

	pddAjax: function() {
		let pddAuthTokenList,
			pddMallIdList,
			shopId,
			mall_id,
			// pati = window.__pati__ || '',
			client_id = "ec5c2241ac5b4c5ca73f08eed1d84b8b",
			loginInfo = topWindow.loginInfo || {},
			delay = $.Deferred(),
			mallDelay = $.Deferred(),
			patiDelay = $.Deferred(),
			finalDelay = $.Deferred(),
			options = arguments[0],
			isJkyun_wdgj = topWindow.jkMainFramePage.plate === "jackyun_wdgj",
			pageCode = window.__pageCode__ || {},
			httpReferer = window.location.href;

		if (isJkyun_wdgj) {
			client_id = "d937304f37824ee3a1b8b4cb2bd8f74c";
		}
		// 如果是吉客云J系列, client_id要使用吉客云的client_id
		if (jkUtils.isJkyun_J()) {
			client_id = "ec5c2241ac5b4c5ca73f08eed1d84b8b";
		}
		// 业务组设置的client_id
		if (options && options.data && options.data.client_id) {
			client_id = options.data.client_id;
		}
		// 获取本地pdd token列表
		try {
			let tokenstr = localStorage.getItem("pddAuthTokenList") || '';
			pddAuthTokenList = JSON.parse(tokenstr);
		} catch (error) {
			pddAuthTokenList = {};
		}
		// 获取本地mall_id列表
		try {
			let mallstr = localStorage.getItem("pddMallIdList") || '';
			pddMallIdList = JSON.parse(mallstr);
		} catch (error) {
			pddMallIdList = {};
		}

		// 处理业务组接口入参
		if (Object.prototype.toString.call(options.data) !== "[object Object]") {
			options.data = {}
		}

		// 根据shopIp从本地获取mall_id
		shopId = options.data.shopId || null;
		if (!shopId) {
			jkUtils.layer({
				state: "warning",
				content: "缺少店铺id",
			});
			return delay.reject();
		}

		// 获取本地pageCode列表
		try {
			let mallstr = localStorage.getItem("pddPlatCodeList") || '';
			pddPlatCodeList = JSON.parse(mallstr);
		} catch (error) {
			pddPlatCodeList = {};
		}
		if (pddPlatCodeList[shopId]?.toUpperCase() === 'PDDKJ') {
			client_id = '96fcef6044064096914e4cc7576ae150'
		}
		mall_id = pddMallIdList[shopId] || null;
		if (!mall_id || !pageCode[shopId]) { // mall_id和pageCode改为一个接口获取
			jkUtils.jkAjax({
				url: "/jkyun/ors/openapi/desensitize/getPlatRequestParam",
				type: "post",
				data: {
					jsonStr: {
						memberName: loginInfo.member_name,
						shopId,
						userId: loginInfo.user_id,
						httpReferer,
						agentShopId: options.data.agentShopId
					}
				}
			}).done((result, msg, code) => {
				if (code === 200) {
					const {
						mallId,
						pageCode: page_code,
						platCode
					} = result.data;
					if (platCode?.toUpperCase() === 'PDDKJ') {
						client_id = '96fcef6044064096914e4cc7576ae150'
					}
					pddPlatCodeList[shopId] = platCode;
					localStorage.setItem("pddPlatCodeList", JSON.stringify(pddPlatCodeList));
					pddMallIdList[shopId] = mallId;
					localStorage.setItem("pddMallIdList", JSON.stringify(pddMallIdList));
					pageCode[shopId] = page_code;
					// window.__pageCode__ = pageCode;
					mallDelay.resolve();
				}
			})
		} else {
			mallDelay.resolve();
		}

		//获取到pageCode之后, 再去获取pati
		mallDelay.always(function() {
			if (!pati) {
				// if (window.PDD_OPEN_init) {
				// 	window.PDD_OPEN_init({
				//
				// 	}).then(function() {
				// 		// 初始化已完成
				// 		window.PDD_OPEN_getPati().then(
				// 			function(patiRes) {
				// 				// 使用 pati
				// 				pati = patiRes;
				// 				window.__pati__ = pati;
				// 				patiDelay.resolve();
				// 			}).catch(() => {
				// 			patiDelay.resolve();
				// 		})
				// 	}).catch(() => {
				// 		patiDelay.resolve();
				// 	});
				// } else {
				// 	patiDelay.resolve();
				// }
			} else {
				patiDelay.resolve();
			}
		});

		//pati是最后一步, 该步骤完成后开始发送真正的数据请求AJAX
		patiDelay.always(function() {
			if (!mall_id) {
				jkUtils.layer({
					state: "warning",
					content: "无效的店铺id",
				});
				return delay.reject();
			}

			if (!pati) {
				const
					errorContent = {
						stack: '拼多多设备指纹信息不存在',
						// filename: window.location.pathname,
						lineno: 1,
						colno: 1,
						// pathname: window.location.pathname,
						moduleName: 'unknown'
					},
					extra = {
						// fileName: window.location.pathname,
						// linkFile: window.location.pathname,
						moduleId: 'unknown',
						errorId: Date.now()
					}
				jkUtils.inspectWeblog('1', JSON.stringify(errorContent), JSON.stringify(extra));
				jkUtils.layer({
					state: "warning",
					content: "拼多多设备指纹信息不存在"
				});
				return delay.reject();
			}

			const code = pageCode[shopId];

			if (!code) {
				const
					errorContent = {
						stack: '拼多多pageCode不存在',
						// filename: window.location.pathname,
						lineno: 1,
						colno: 1,
						// pathname: window.location.pathname,
						moduleName: 'unknown'
					},
					extra = {
						// fileName: window.location.pathname,
						// linkFile: window.location.pathname,
						moduleId: 'unknown',
						errorId: Date.now()
					}
				jkUtils.inspectWeblog('1', JSON.stringify(errorContent), JSON.stringify(extra));
			}

			options.headers = {
				"X-PDD-VerifyAuthToken": pddAuthTokenList[mall_id] || '',
			}
			// 测试风控验证流程，可以始终触发风控
			if (options.isMustVertify) {
				options.headers["X-PDD-MustVerify"] = "True";
			}

			options.headers["X-PDD-Pati"] = pati;
			options.headers["X-PDD-PageCode"] = code;

			options.url = options.url.replace(/apipdd/, 'frontpdd');
			options.contentType = "application/json";
			$.extend(options.data, {
				client_id: client_id,
				mall_id: mall_id,
				username: loginInfo.member_name,
			});
			options.data = JSON.stringify(options.data);

			let ajax = $.ajax(options);
			ajax.then(function() {
				// arguments = [res, state, jqXHR]
				let args = arguments,
					res = args[0];

				if (res.error_code) return finalDelay.reject(res);

				// 返回 (res, jqXHR.msg, jkXHR)
				return finalDelay.resolveWith(this, args);
			}).then(null, function() {
				let args = arguments,
					deferred = $.Deferred(),
					defaultAjaxOptions = jkUtils.jkDefaultAjaxOptions;

				if (options.forceUseErrorHandler === false) return deferred.rejectWith(this,
					args);

				if (args.length === 1) {
					let error = args[0];
					// 触发风控
					if (error.error_code == "54001") {
						let token = error.risk_info ? error.risk_info.verify_auth_token : "",
							redirect_url = encodeURIComponent(location.origin +
								`/common/pdd_vertify.html?mall_id=${mall_id}`);

						try {
							let url =
								`https://fuwu.pinduoduo.com/service-market/are-you-robot?mall_id=${mall_id}&client_id=${client_id}&verifyAuthToken=${token}&redirect_url=${redirect_url}`;
							localStorage.setItem("mall_id", mall_id);
							// isInCef ? window.jk_MultiWindow(url, "") : window.open(url,
							// 	'_blank');
						} catch (error) {}
						return finalDelay.reject(error);
					}

					jkUtils.layer({
						state: "warning",
						content: error.error_msg,
					});
					return finalDelay.rejectWith(this, args);
				}

				// 统一处理错误 arguments = [jqXHR, state, msg];
				let jqXHR = args[0],
					status = jqXHR.status.toString(),
					error = jkUtils.jkParseJson(jqXHR.responseText);

				if (args[2] === "abort") {
					return finalDelay.rejectWith(this, args);
				}
				// 处理接口超时,接口超时status=0
				if (args[1] === "timeout") {
					mini.showTips({
						content: "请求超时, 请稍后重试, 接口地址: " + options.url,
						state: "warning",
						y: 'center',
						timeout: 4000
					});
					args[0].abort();
					return finalDelay.reject(error);
				}
				// 不明原因的错误，像接口取消之类的
				if (status == 0) {
					// 断网处理
					// if (!window.navigator.onLine) {
					// 	jkUtils.offlineErrorLayer();
					// }
					return finalDelay.reject(error);
				}
				// 统一处理404 500等错误
				if (Object.prototype.toString.call(error) !== "[object Object]") {
					defaultAjaxOptions.netError({
						url: options.url,
						content: `请求失败: ${jqXHR.statusText}`,
						status: jqXHR.status,
					});
					return finalDelay.rejectWith(this, args);
				}
			});
			finalDelay.abort = function() {
				return ajax.abort.apply(ajax, arguments);
			}
		})

		return finalDelay;
	},
	ajax: function() {
		var options = $.extend({}, jkUtils.jkDefaultAjaxOptions);

		if (arguments.length === 1) {
			$.extend(options, arguments[0]);
		} else if (arguments.length === 2) {
			$.extend(options, arguments[1]);
		}
		options.jqXHR = null;
		var ajaxBeginTime = (new Date()).valueOf();
		//记录ajax请求开始的时间
		options.ajaxBeginTime = ajaxBeginTime;
		var ajax = $.ajax.apply($, arguments).then(function(res) {
			var deferred = $.Deferred();
			//将jQuery的ajax对象绑定在options上, 以便后续记入web日志处理
			if (options.jqXHR === null) {
				options.jqXHR = arguments[2];
			}
			// 处理请求静态json文件，比如menuList.json
			if ($.isArray(res)) {
				return deferred.resolveWith(this, arguments);
			}

			// code != 200 返回错误结果，会跳到下面的fail
			if (res.code != jkEnums.CommonRspCode.Success) {
				return deferred.reject(res);
			}

			// 返回结果，useDataFilter 表示是否开启对返回的数据重组，
			if (options.useDataFilter) {
				// 重组数据，返回(res.result, res.msg, res.code, res)
				return deferred.resolveWith(this, [res.result, res.msg, res.code, res]);
			}
			return deferred.resolveWith(this, arguments);
		}).then(null, function() {
			// 第一个参数为null的.then回调，是处理错误的请求，可以拦截ajax.fail
			var args = arguments,
				deferred = $.Deferred();
			//将jQuery的ajax对象绑定在options上, 以便后续记入web日志处理
			if (options.jqXHR === null) {
				options.jqXHR = args[0];
			}
			if (args.length < 1) {
				jkUtils.showTip({
					content: "未知错误！",
					state: "warning",
				});
				return deferred.rejectWith(this, args);
			}

			// 处理 res.code==500 的错误，// arguments = [error];
			if (args.length === 1) {
				// 配置了 useErrorHandler=true 使用架构统一错误处理
				if (options.useErrorHandler) {
					options.serverErrorHandler(args[0], {
						url: options.url,
						dataString: options.data,
					});
				}
				return deferred.rejectWith(this, args);
			}
			if (args[2] === "abort") {
				return deferred.rejectWith(this, args);
			}
			if (args[1] === "timeout") {
				mini.showTips({
					content: "请求超时, 请稍后重试, 接口地址: " + options.url,
					state: "warning",
					y: 'center',
					timeout: 4000
				});
				return deferred.rejectWith(this, args);
			}

			// 统一处理 401 403 404 452 500 502 504 等异常情况 arguments = [jqXHR, status, msg];
			var jqXHR = args[0],
				status = jqXHR.status,
				statusHandler = ["403", "452", "453"],
				errorHandler = jkUtils.jkDefaultAjaxOptions.errorHandler,
				error = jkUtils.jkParseJson(jqXHR.responseText);

			error = error ? error : {};

			// 架构固定统一处理 0 403 452 错误
			if (statusHandler.indexOf(status.toString()) >= 0) {
				errorHandler[status.toString()](error);
				return deferred.rejectWith(this, args);
			}

			// 统一处理 401 错误
			if (status === 401) {
				if (error.subCode !== '0190210003') {
					// 接口入参未计算签名
					jkUtils.showTip({
						content: error.msg,
						state: 'warning'
					});
					return deferred.reject(error);
				}
				// 客户端会自动更新token，如果token过期 => 锁屏
				if (jkUtils.jkInCEF()) {
					// 在客户端
					if (topWindow.isLocked) {
						return deferred.reject(error);
					}
					// 清除自动锁屏的定时器
					clearTimeout(topWindow.lockTimer);
					topWindow.isLocked = true;

					jkUtils.showTip({
						content: jkUtils.jki18n('base_msg_100001', '登录超时，请重新登录！', 'base'),
						state: 'warning'
					}, function() {
						topWindow.lock();
					});
					return deferred.reject(error);
				}
				// 在京东或阿里环境
				if (["jackyun_ali", "jd_web"].indexOf(topWindow.jkMainFramePage.plate) >= 0) {
					jkUtils.showTip({
						content: jkUtils.jki18n('base_msg_100001', '登录超时，请重新登录！', 'base'),
						state: 'warning'
					});
					return deferred.reject(error);
				}
				// 这个方法web版无法自动刷新token，签名计算由业务组执行，拿不到原始的接口入参数据
				jkUtils.jkDefaultAjaxOptions.loginOut();
				return deferred.reject(error);
			}

			if (options.useErrorHandler) {
				// 暂时不处理服务器异常，影响本地开发
				// errorHandler["default"].apply(null, args);
			}
			return deferred.rejectWith(this, args);
		}).always(function() {
			var ajaxEndTime = (new Date()).valueOf();
			//记录ajax请求开始的时间
			options.ajaxEndTime = ajaxEndTime;
			//本次ajax返回所用的绝对时间(单位:秒)
			var absoluteTime = options.ajaxEndTime - options.ajaxBeginTime;
			//进入记录ajax的web日志的流程
			jkUtils.writeAjaxWeblog(options, absoluteTime);
		});
		return ajax;
	},

	offlineErrorLayerFlag: false,
	offlineErrorLayer: function() { //网络异常 错误弹框
		if (jkUtils.offlineErrorLayerFlag) return; //避免弹框重复
		jkUtils.layer({
			state: 'error',
			content: '网络异常，请检查网络之后重试！',
			onload: function() {
				jkUtils.offlineErrorLayerFlag = true;
			},
			callback: function() {
				jkUtils.offlineErrorLayerFlag = false;
			}
		});
	},

	// 绑定事件监听
	addEvent: function(el, type, callback) {
		// if (window.attachEvent) {
		// 	el.attachEvent('on' + type, callback);
		// 	return;
		// }
		el.addEventListener(type, callback);
	},

	// 监控网络状态
	bindOfflineEvent: function() {
		let that = this,
			loginHost = loginInfo && loginInfo.loginResult && loginInfo.loginResult.host || '';
		that.netWorkTimmer = null;
		that.onlineFlag = true;
		// if (window.Offline && loginHost) {
		// 	window.Offline.options = {
		// 		game: true,
		// 		reconnect: { //在连接断开时定期自动重新测试
		// 			initialDelay: 2, //(秒)   连接断开2秒后开始重试
		// 			delay: 2 //(秒)    两次重试之间时间间隔
		// 		},
		// 		checkOnLoad: true,
		// 		interceptRequests: false,
		// 		requests: false,
		// 		checks: {
		// 			image: {
		// 				url: loginHost + '/login/img/logo_J.png'
		// 			},
		// 			active: 'image'
		// 		}
		// 	}
		// 	window.Offline.on('up', function() {
		// 		onCallBack();
		// 	})
		// 	window.Offline.on('down', function() {
		// 		offCallBack();
		// 	})
		// } else {
		// 	jkUtils.addEvent(that, "online", function() {
		// 		if (that.netWorkTimmer) {
		// 			clearTimeout(that.netWorkTimmer);
		// 		}
		// 		/*
		// 		 * 处理window.online不准确的问题，手动断开网络再重连的时候，网络其实还没有马上
		// 		 * 连上，但是online事件已经触发。
		// 		 */
		// 		that.netWorkTimmer = setTimeout(function() {
		// 			onCallBack();
		// 		}, 2000);
		// 	});
		// 	jkUtils.addEvent(that, "offline", function() {
		// 		if (that.netWorkTimmer) {
		// 			clearTimeout(that.netWorkTimmer);
		// 		}
		// 		offCallBack();
		// 	});
		// }

		function onCallBack() {
			jkUtils.layer({
				content: '网络已连接',
			});
			that.onlineFlag = true;
			$('#offline-info').hide();
		}

		function offCallBack() {
			that.onlineFlag = false;
			jkUtils.showTip({
				content: '网络异常，请检查网络之后重试！',
				state: 'warning',
				timeout: 5000,
			});
			$('#offline-info').show();
		}

	},
	defaultErpSelectorOptions: {
		// targetWindow: window,
		url: '/common/goods/goods_selector.html',
		goodsUrl: '/jkyun/erp/goods/getgoodsinfoandunit',
		ajaxType: 'get',
		goodsParams: {},
		skuUrl: '/jkyun/erp/goods/getskulist',
		skuParams: {},
		title: '请选择货品', //jkUtils.jki18n('base_msg_100006', '请选择货品', 'base'),
		width: '70%',
		height: '90%',
		showModal: true,
		allowResize: false,
		selectdGoods: [],
		mode: 'multi'
	},
	// 打开分享模板页
	openShareTemplate: function(config) {
		if (!config || !$.isArray(config.datas)) return;

		let title = "模板管理" + (config.pageName ? ` - ${config.pageName}` : "");
		let pop = jkUtils.openPopPage({
			targetWindow: config.targetWindow,
			title: title,
			url: "/jk_mini/share_template.html",
			width: 1200,
			height: "92%",
			showMaxButton: true,
			onload: function() {
				let iframe = this.getIFrameEl().contentWindow;
				iframe.setData({
					thisPop: pop,
					pageType: config.pageType,
					templateId: config.templateId,
					htmlPath: config.htmlPath,
					origin: config.origin,
					datas: config.datas,

					fieldGridId: config.fieldGridId,
					showViewTitle: config.showViewTitle,
					getFieldDataFn: config.getFieldDataFn,
					showLogo: config.showLogo,
					logoWidth: config.logoWidth,
					logoHeight: config.logoHeight,
					showSetting: config.showSetting,
				});
			},
			ondestroy: config.ondestroy,
		});
		return pop;
	},
	// 打开分享配置页
	openShareLink: function(config) {
		if (!config.data) return;

		return jkUtils.openPopPage({
			targetWindow: config.targetWindow,
			title: config.title || "分享链接",
			url: "/jk_mini/share_link.html",
			width: config.width || 640,
			height: config.height || 360,
			onload: function() {
				let iframe = this.getIFrameEl().contentWindow;
				iframe.setData(config.data);
			},
			ondestroy: config.ondestroy,
		});
	},
	openErpGoodsSelector: function(options) {
		var deferred = $.Deferred();
		var oldCss = $('html').css('overflow-y');
		$('html').css('overflow-y', 'hidden');
		options = $.extend({}, jkUtils.defaultErpSelectorOptions, options);
		options = $.extend(options, {
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.contextDeferred && iframe.contentWindow.contextDeferred
					.resolve({
						selectdGoods: mini.clone(options.selectdGoods),
						mode: options.mode,
						goodsUrl: options.goodsUrl,
						ajaxType: options.ajaxType,
						goodsParams: options.goodsParams,
						skuUrl: options.skuUrl,
						pageOrigin: options.pageOrigin, //mrp添加入口页面标识，选择商品页面选择结果表格添加列
						skuParams: options.skuParams,
						skuExCells: options.skuExCells,
						isBatchManagement: options.isBatchManagement,
						code: options.code, // 用于跨货主调用
						deferred: deferred,
						callback: options.callback, //crm用于判断货品是否被关联过卡券
						goodsBatch: options.goodsBatch,
						goodsBatchStock: options.goodsBatchStock,
						maxGoodsLength: options.maxGoodsLength, //选择数量
						isShoppingCartMode: options.isShoppingCartMode || false
					});
			}
		});
		jkUtils.inputFrame = mini.open(options);
		deferred.done(function() {
			$('html').css('overflow-y', oldCss);
		});

		return deferred.promise();
	},
	//规格组合选择页面
	openErpSkuSelector: function(options, func) {
		if (!options && typeof options != 'object') return console.log('请传入参数');
		mini.open({
			// targetWindow: window,
			url: '/erp/goods_skuselect.html',
			title: options.title || jkUtils.jki18n('base_msg_100007', '规格组合选择', 'base'),
			width: options.width || 900,
			height: options.height || 600,
			allowResize: false,
			showModal: true,
			onload: function() {
				var iframe = this.getIFrameEl();
				var data = options.data;
				iframe.contentWindow.SkuSelect.setData(data);
			},
			ondestroy: function(message) {
				if (message === 'ok') {
					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.SkuSelect.getData();
					if (func && typeof func === 'function')
						func(data);
				}
			}
		});
	},
	//货品详细页面
	openErpSkuDetails: function(options) {
		if (!options && typeof options != 'object') return console.log('请传入参数');
		mini.open({
			// targetWindow: options.window || window,
			url: '/erp/goods_details.html',
			title: options.title || jkUtils.jki18n('base_msg_100008', '货品详情', 'base'),
			width: options.width || 1200,
			height: options.height || 628,
			allowResize: false,
			showModal: true,
			showMaxButton: true,
			onload: function() {
				var iframe = this.getIFrameEl();
				var data = options.data;
				iframe.contentWindow.SkuDetails.setData(data);
			},
			ondestroy: function() {

			}
		});
	},
	/**
	 * 选择订单
	 * @param {Object} options  配置多选单选
	 * @param {Function} func  回调函数
	 */
	openOrderSelector(options, func) {
		var defaultOption = {
			// targetWindow: window,
			title: jkUtils.jki18n('base_msg_100009', '选择订单', 'base'),
			width: '96%',
			height: '96%',
			url: '/common/order/order_selected.html',
			defaultParams: {}, //默认查询条件
			//默认字段，如果不传非必要字段接口不返回
			defaultCols: ['tradeOrder.orderNo', 'tradeOrder.tradeStatus', 'tradeOrderCustomer.customerId',
				'tradeOrder.payment', 'tradeOrderInvoice.invoiceStatus'
			],
			defaultGoodsCols: ['specName', 'unit', 'taxFee', 'taxRate', 'discountFee', 'discountRate',
				'goodsId', 'subTradeId', 'goodsAttribute', 'isFit', 'isGift', 'fitSubTradeId',
				'refundStatus'
			],
			orderMulti: true, //订单是否多选
			goodsMulti: true, //货品是否多选
			showHeader: true,
			showModel: true,
			allowResize: false,
			showCloseButton: true,
		};
		options = $.extend(defaultOption, options, true);
		options = $.extend(options, {
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.orderSelectorPage.setData(options);
			},
			ondestroy: function(message) {
				if (message === 'ok') {
					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.orderSelectorPage.getData();
					if (func && typeof func === 'function')
						func(data);
				}
			}
		}, true);
		mini.open(options);
	},
	/**
	 * 售后单
	 * @param {Object} options  配置多选单选
	 * @param {Function} func  回调函数
	 */
	openAfterSaleSelector(options, func) {
		var defaultOption = {
			// target: window,
			title: jkUtils.jki18n('base_msg_100010', '选择售后单', 'base'),
			width: '96%',
			height: '96%',
			url: '/oms/after_sale/after_sale_select.html',
			defaultParams: {}, //默认查询条件
			afterSaleMulti: false, //订单是否多选
			showHeader: true,
			showModel: true,
			allowResize: false,
			showCloseButton: true,
		};
		options = $.extend(defaultOption, options, true);
		options = $.extend(options, {
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.afterSale.setData(options);
			},
			ondestroy: function(message) {
				if (message == 'ok') {
					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.afterSale.getData();
					if (func && typeof func === 'function')
						func(data);
				}
			}
		}, true);
		mini.open(options);
	},

	checkformDataChange: function(data) {
		var list = [];
		if ($.isArray(data)) {
			list = data;
		} else {
			list.push(data);
		}

		var len = list.length;
		var flag = false;
		for (var i = 0; i < len; i++) {
			var l = list[i];
			if ((l.new) && (l.old)) {
				if (!this.compareObjWith(l)) {
					flag = true;
					break;
				}
			}
		}

		return flag;
	},

	//检查表单是否可关闭
	formCanClose: function(data, options = {}) {
		return new Promise(function(resolve) {
			var result = ((options) && (options.showCloseTip)) || jkUtils.checkformDataChange(data);
			if (result) {
				if ($('.mini-messagebox').length === 0) {
					// window.parent.mini.confirm(options.customTip || jkUtils.jki18n('base_msg_100011',
					// 		'数据未保存，是否退出？', 'base'), jkUtils.jki18n('com_tip', '提示', 'common'),
					// 	function(action) {
					// 		if (action === 'ok') {
					// 			resolve(true);
					// 		} else resolve(false);
					// 	});
				}
			} else {
				resolve(true);
			}
		});
	},

	//表单初始化 myInit定义自己的初始化东西
	initform: function(myInit) {
		$('.mini-tips').remove();
		if (myInit) myInit();
	},

	//显示窗口是否正在加载
	showFormLoading: function(flag) {
		if (!flag) {
			jkUtils.showTip({
				state: 'warning',
				content: jkUtils.jki18n('base_msg_100012', '页面加载中，请稍后！', 'base')
			});
			return true;
		} else return false;
	},

	//输入框回车，等遍历完所有输入框，不用回到第一个输入框1
	formInputEnter(inputControls, finishLoopCallBack, formID) {
		this.formInputLoop(inputControls, true, finishLoopCallBack, formID);
	},

	//输入框回车，等遍历完所有输入框，回到第一个输入框1
	formInputEnterWithLoop(inputControls, finishLoopCallBack, formID) {
		this.formInputLoop(inputControls, false, finishLoopCallBack, formID);
	},

	formInputLoop(inputControls, isStopLoop, finishLoopCallBack, formID) {
		let form = formID || document.body;
		$(form).on('keyup', '.mini-textbox-input,.mini-buttonedit-input', function(e) {
			// var event = e || window.event;
			// if (event.keyCode == 13) {
			// 	var el = $(this);
			// 	var nextControl = getNextInput(el[0]);
			// 	if (nextControl)
			// 		nextControl.focus();
			// 	else {
			// 		if (finishLoopCallBack) {
			// 			finishLoopCallBack();
			// 		}
			//
			// 		if (!isStopLoop)
			// 			inputControls[0].focus();
			// 	}
			// }
		});

		function getNextInput(current) {
			var nextInput = null;
			$.each(inputControls, function(i, control) {
				if (current == control._textEl) {
					var nextControlArr = inputControls.slice(i + 1);
					$.each(nextControlArr, function(j, nextControl) {
						if (nextControl && nextControl.getEnabled()) {
							nextInput = nextControl;
							return false;
						}
					});
					return false;
				}
			});

			return nextInput;
		}
	},

	printData: function(url, data, type) {
		if (!type) type = 'get';
		var param = {
			url: url,
			type: type
		};

		if (type === 'get')
			param.url += '?' + jkUtils.jkGetSign(data);
		else
			param.data = jkUtils.jkGetSign(data);

		this.ajax(param).then(function(result, msg) {
			if (msg)
				return jkUtils.showTip2({
					content: msg
				});
		});
	},

	// 文件导入
	fileImport: {
		dtd: null,

		// 打开文件导入选择器界面
		excelSelector: function(config) {
			var _this = this;
			mini.open({
				// targetWindow: window,
				url: config.url ? config.url : '/common/excel_import/excel_selector.html',
				title: config.title || jkUtils.jki18n('com_excel_import', 'Excel导入', 'common'),
				width: config.width || 400,
				height: config.height ? config.height : 170,
				showModal: true,
				allowResize: false,
				onload: function() {
					var iframe = this.getIFrameEl();
					iframe.contentWindow.setData(config);
				},
				ondestroy: function(action) {

					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.getData();
					if (action === 'ok') {
						data.defaultParams = config.defaultParams; //add by zhongbin 2018/8/17
						data.requiredGroup = config.requiredGroup; // 用于分组必填映射的功能
						data.extraFieldMap = config.extraFieldMap;
						data.urlProgressTip = config.urlProgressTip; //add 导入进度提示url fengwei
						data.excelFiledMapHeight = config.excelFiledMapHeight; //add 匹配字段页面高度 wangss
						data.verifyUrl = config.verifyUrl; // 后端自定义校验url add by fenglin 2020/2/19
						data.verifyImportUrl = config.verifyImportUrl;
						// data.window = config.window;
						data.showMaxButton = !!config.showMaxButton;
						jkUtils.fileImport.excelFiledMap(data);
					} else if (action === 'mrp') {
						_this.dtd.resolve(data);
					}
				}
			});
			this.dtd = $.Deferred();
			return this.dtd;
		},

		/**
		 * @description: 批量导入
		 * @param {Object} config 参数
		 *                 {
		 *                    moduleCode {String} 模块code
		 *                    sysCode {String} 业务code，例如WMS、OMS等
		 *                    templateCode {String} 模板标准头code，例如ReceiveHeaders
		 *                    versionCode {String} 版本标识，区分模板
		 *                    downloadUrl {String} 自定义模板地址
		 *                    maxFile {Number} 最大文件数，默认50
		 *                    maxData {Number} 最大数据量，默认50000
		 *                    importUrl {String} excel导入的url
		 *                    importParams {Object} 导入接口需要的参数，例如货主、仓库等数据
		 *                    getHeaderUrl {String} 获取标准头的接口
		 *                    successCallback(id) {Function} 处理数据回调, 参数id：任务id
		 *                 }
		 */
		excelBatchSelector: function(config) {
			// if (!window.jk_excel_get_history) {
			// 	console.log('该版本不支持批量导入')
			// 	return
			// }
			if (!config.moduleCode) {
				throw new Error('moduleCode 为空！')
			}
			const selectorWindow = mini.open({
				// targetWindow: window,
				url: '/common/excel_import/excel_batch_selector.html',
				title: jkUtils.jki18n('com_excel_batch_import', '批量导入历史记录', 'common'),
				width: 850,
				height: 515,
				showModal: true,
				allowResize: false,
				onload: function() {
					let iframe = this.getIFrameEl();
					iframe.contentWindow.setData(config, selectorWindow);
				},
				ondestroy: function() {}
			});
		},

		// 处理字段匹配界面
		excelFiledMap: function(data) {
			var _this = this;
			var urlProgressTip = data.urlProgressTip;
			mini.open({
				// targetWindow: data.window ? data.window : window,
				url: data.ismultisheet ? '/common/excel_import/excel_import_field_multisheet.html' :
					'/common/excel_import/excel_import_field_map.html',
				title: jkUtils.jki18n('base_msg_100015', '表格字段匹配', 'base'),
				width: 600,
				height: data.excelFiledMapHeight || 550,
				showModal: true,
				allowResize: false,
				showMaxButton: data.showMaxButton,
				onload: function() {
					var iframe = this.getIFrameEl();
					iframe.contentWindow.setData(data);
				},
				ondestroy: function(action) {
					if (action === 'ok') {
						var iframe = this.getIFrameEl();
						var data = iframe.contentWindow.getData();
						if (data.sysCode == 'OA' || (data.sysCode === 'ERP' && (typeof erpUtils ===
								'undefined' || !erpUtils.isAsynImportModule(data.moduleCode))) ||
							data.sysCode === 'ERP-BUSIORDER' || (typeof wmsUtils !== 'undefined' &&
								wmsUtils.doesShowImportResult(data.moduleCode) || data.sysCode ===
								'ERP-PURCH') || (data.sysCode === 'GSP' && (typeof gspUtils ===
								'undefined' || !gspUtils.isAsynImportModule(data.moduleCode))))
							jkUtils.fileImport.excelImportResult(data.result);
						else if (data.sysCode === 'ACS') {
							if (typeof acsUtils !== 'undefined' && acsUtils.isAsynImportModule(data
									.moduleCode)) {
								jkUtils.showTip({
									content: '账单正在执行导入，请到任务中心页面查看'
								});
								_this.dtd.resolve(data.result);
							} else {
								jkUtils.fileImport.excelImportResult(data.result);
							}
							// jkUtils.fileImport.excelImportResult(data.result);
						} else if (data.sysCode === 'MRP') {
							data.urlProgressTip = urlProgressTip;
							jkUtils.fileImport.excelImportResult(data);
						} else if (data.sysCode === 'FIN' || data.sysCode === 'FIN_FBS') {
							if (typeof finUtils !== 'undefined' && typeof finUtils
								.isAsynImportModule === 'function' && finUtils.isAsynImportModule(
									data.moduleCode)) {
								_this.dtd.resolve(data.result);
							} else {
								jkUtils.fileImport.excelImportResult(data.result);
							}
						} else
							_this.dtd.resolve(data.result);
					}
				}
			});
		},

		// 展示导入结果界面
		excelImportResult: function(data) {
			var _this = this;
			mini.open({
				// targetWindow: window,
				url: '/common/excel_import/excel_import_result.html',
				title: jkUtils.jki18n('base_msg_100016', 'Excel导入结果', 'base'),
				width: 500,
				height: 250,
				showModal: true,
				allowResize: false,
				showCloseButton: false,
				onload: function() {
					var iframe = this.getIFrameEl();
					iframe.contentWindow.setData(data);
				},
				ondestroy: function() {
					_this.dtd.resolve(data);
				}
			});
		}
	},

	// 文件导出接口
	fileExport: {
		startExcelExport: function(data) {
			var params = {};
			// params.serverName = "wms-huangyi/wms"

			//excelType  headersJson conditionJson  各个模块传入
			params.serverName = data.serverName ? data.serverName : 'wms/wms';
			params.excelType = data.excelType;
			params.headersJson = data.headersJson;
			params.conditionJson = data.conditionJson;
			//2022-01-10多sheet导出增加各个sheet命名参数
			if ('sheetNames' in data) {
				params.sheetNames = data.sheetNames;
			}
			if ('isMerge' in data) {
				params.isMerge = data.isMerge;
			}
			var Async = data.Async ? data.Async : false;
			for (var key in data.conditionJson) {
				if (data.conditionJson[key] instanceof Date) data.conditionJson[key] = mini.formatDate(data
					.conditionJson[key], 'yyyy/MM/dd HH:mm:ss');
			}
			params.typeName = data.typeName;
			params.multiSheet = data.multiSheet ? true : false;
			params.exportTotal = data.exportTotal ? data.exportTotal : '';
			//2020-12-07明文导出参数添加
			if (data.plaintext === true) {
				params.plaintext = data.plaintext;
			}
			//如果数据大于10000条或者设置了Async为true,则执行异步导出
			if (data.totalRows >= 10000 || Async === true) {
				params.isSyn = 'false'; //异步
				jkUtils.jkAjax({
					url: data.url || '/jkyun/excel-service/manager/startExcelExport',
					type: 'post',
					data: params,
					timeout: 120000
				}).always(() => {
					mini.unmask(document.body);
				});
				//否则执行同步逻辑
			} else {
				params.isSyn = 'true'; //同步
				mini.mask({
					el: document.body,
					cls: 'mini-mask-loading',
					html: jkUtils.jki18n('base_msg_100017', '任务进行中...', 'base')
				});
				jkUtils.jkAjax({
					url: data.url || '/jkyun/excel-service/manager/startExcelExport',
					type: 'post',
					data: params,
					timeout: 120000
				}).done((data) => {
					jkUtils.downloadFile(data.data);
					mini.unmask(document.body);

				}).always(() => {
					mini.unmask(document.body);
				});
			}
		},
		/**
		 * @description: 前端导出当前页(单表导出)  不支持导出图片
		 * @param {Object} config 参数
		 * {
		 * enName {Arrary} 字段field 组成的数组
		 * showName {Arrary} 字段文本组成的数组，顺序与enName字段对应
		 * gridData {Arrary} 表格数据
		 * imgFields {Arrary} 图片列field 组成的数组,导出时将本列数据置空
		 * timeFields {Arrary} 时间戳列field 组成的数组
		 * trueValueObj {Object} 需要根據字段值导出其他文本 eg:smsType对应的字段值为1时，导出文本为'营销类'；smsType对应的字段值为2时，导出文本为'通知类'
		 *    trueValueObj:{
		                'smsType':[{value:1,text:'营销类'},{value:2,text:'通知类'}]
		            },
		 * name {String} 文件名
		 * gridDataItemArraryFlag {Boolean} 默认false, false: gridData表格数据为形如[{},{}]的数据，子项为表格行数据key-value组成的json对象； true: gridData表格数据为二维数组，子项为表格行数据,数据顺序与enName字段对应。
		 *}
		 */
		jsExportToExcel: function(config) {
			// if (window.XLSX && window.xlsxStyle) {
			// 	creatExcel();
			// } else {
			// 	let loadLength = 2;
			// 	jkUtils.loadJs('/component/xlsx.core.min.js', loadJsCallback)
			// 	jkUtils.loadJs('/component/xlsxStyle.core.min.js', loadJsCallback)
			//
			// 	function loadJsCallback() {
			// 		loadLength--;
			// 		loadLength === 0 && creatExcel();
			// 	}
			// }

			function creatExcel() {
				let defauleConfig = {
					enName: [],
					showName: [],
					gridData: [],
					imgFields: [],
					timeFields: [],
					trueValueObj: {},
					name: '表格导出',
					gridDataItemArraryFlag: false
				}
				let execlConfig = $.extend({}, defauleConfig, config);
				const {
					enName,
					name,
					gridDataItemArraryFlag
				} = execlConfig;
				const worker = new Worker('/common/jsExportToExcelData.js');
				mini.mask({
					el: document.body,
					cls: 'mini-mask-loading',
					html: jkUtils.jki18n('base_msg_100017', '任务进行中...', 'base')
				})
				worker.postMessage(execlConfig);
				worker.onmessage = msg => {
					if (msg.data !== 'error') {
						createExecl(msg.data)
					}
					worker.terminate();
					mini.unmask();
				}
				worker.onerror = () => {
					worker.terminate();
					mini.unmask();
				}

				function createExecl(data = []) {
					let workbook = {
						SheetNames: [name],
						Sheets: {}
					};
					let worksheet = gridDataItemArraryFlag ? XLSX.utils.aoa_to_sheet(data) : XLSX.utils
						.json_to_sheet(data, {
							header: enName,
							skipHeader: true
						})
					workbook.Sheets[name] = worksheet;
					// 生成excel的配置项
					let wopts = {
						bookType: 'xlsx', // 要生成的文件类型
						bookSST: false, // 是否生成Shared String Table，官方解释是，如果开启生成速度会下降，但在低版本IOS设备上有更好的兼容性
						type: 'binary'
					};

					// 设置表格样式
					jkUtils.xlsxExportFunConfig.setDefaultStyle(workbook, name);
					let wbout = xlsxStyle.write(workbook, wopts);
					let blob = new Blob([s2ab(wbout)], {
						type: "application/octet-stream"
					});
					let execlUrl = URL.createObjectURL(blob);
					jkUtils.downloadFile(execlUrl, name + '.xlsx');
					// 字符串转ArrayBuffer
					function s2ab(s) {
						let buf = new ArrayBuffer(s.length);
						let view = new Uint8Array(buf);
						for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
						return buf;
					}
				}
			}
		},
		/**
		 * @description: 前端导出当前页(多表导出)  不支持导出图片
		 * @param {Object} config 参数
		 * {
		 * excelName: {String} excel文件名 默认： 表格导出
		 * excelConfig{Arrary} 多表配置
		 * 其中，单个excel配置:
		 *    enName {Arrary} 字段field 组成的数组
		 *    showName {Arrary} 字段文本组成的数组，顺序与enName字段对应
		 *    gridData {Arrary} 表格数据
		 *    imgFields {Arrary} 图片列field 组成的数组,导出时将本列数据置空
		 *    timeFields {Arrary} 时间戳列field 组成的数组
		 *    trueValueObj {Object} 需要根據字段值导出其他文本 eg:smsType对应的字段值为1时，导出文本为'营销类'；smsType对应的字段值为2时，导出文本为'通知类'
		 *        trueValueObj:{
		                'smsType':[{value:1,text:'营销类'},{value:2,text:'通知类'}]
		            },
		 *    name {String} sheet名 默认sheet1 、sheet2 ......
		 *    gridDataItemArraryFlag {Boolean} 默认false, false: gridData表格数据为形如[{},{}]的数据，子项为表格行数据key-value组成的json对象； true: gridData表格数据为二维数组，子项为表格行数据,数据顺序与 enName字段对应。}
		 *
		 */
		jsExportToMultiSheetExcel: function(config = {}) {
			let {
				excelConfig,
				excelName = '表格导出'
			} = config;
			if (!jkUtils.jkIsArray(excelConfig)) return;
			// if (window.XLSX && window.xlsxStyle) {
			// 	creatExcel();
			// } else {
			// 	let loadLength = 2;
			// 	jkUtils.loadJs('/component/xlsx.core.min.js', loadJsCallback)
			// 	jkUtils.loadJs('/component/xlsxStyle.core.min.js', loadJsCallback)
			//
			// 	function loadJsCallback() {
			// 		loadLength--;
			// 		loadLength === 0 && creatExcel();
			// 	}
			// }

			function dealSheetData() {
				excelConfig = excelConfig.map((sheetItem, index) => {
					let defauleConfig = {
						enName: [],
						showName: [],
						gridData: [],
						imgFields: [],
						timeFields: [],
						trueValueObj: {},
						name: 'sheet' + index,
						gridDataItemArraryFlag: false
					}
					return $.extend({}, defauleConfig, sheetItem)
				})
			}

			function creatExcel() {
				dealSheetData();
				const worker = new Worker('/common/jsExportToExcelData.js');
				mini.mask({
					el: document.body,
					cls: 'mini-mask-loading',
					html: jkUtils.jki18n('base_msg_100017', '任务进行中...', 'base')
				})
				worker.postMessage({
					multiSheetFlag: true,
					excelConfig: excelConfig
				});
				worker.onmessage = msg => {
					if (msg.data !== 'error' && jkUtils.jkIsArray(msg.data)) {
						createExecl(msg.data)
					}
					worker.terminate();
					mini.unmask();
				}
				worker.onerror = () => {
					worker.terminate();
					mini.unmask();
				}

				function createExecl(excelData = []) {
					let workbook = {
						SheetNames: [],
						Sheets: {}
					};

					excelData.forEach(item => {
						const {
							enName,
							name,
							gridDataItemArraryFlag,
							data
						} = item;
						let worksheet = gridDataItemArraryFlag ? XLSX.utils.aoa_to_sheet(data) : XLSX
							.utils.json_to_sheet(data, {
								header: enName,
								skipHeader: true
							})
						workbook.SheetNames.push(name);
						workbook.Sheets[name] = worksheet;
						// 设置表格样式
						jkUtils.xlsxExportFunConfig.setDefaultStyle(workbook, name);
					})

					// 生成excel的配置项
					let wopts = {
						bookType: 'xlsx', // 要生成的文件类型
						bookSST: false, // 是否生成Shared String Table，官方解释是，如果开启生成速度会下降，但在低版本IOS设备上有更好的兼容性
						type: 'binary'
					};
					let wbout = xlsxStyle.write(workbook, wopts);
					let blob = new Blob([s2ab(wbout)], {
						type: "application/octet-stream"
					});
					let execlUrl = URL.createObjectURL(blob);
					jkUtils.downloadFile(execlUrl, excelName + '.xlsx');
					// 字符串转ArrayBuffer
					function s2ab(s) {
						let buf = new ArrayBuffer(s.length);
						let view = new Uint8Array(buf);
						for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
						return buf;
					}
				}
			}
		},

		convertFieldToArr: function(obj, fieldArr) {
			fieldArr.forEach(function(key) {
				obj[key] = $.trim(obj[key]);
				if (obj[key]) {
					obj[key] = obj[key].length > 0 ? obj[key].split(',') : [];
				} else if (obj[key] === '')
					obj[key] = [];
				// else if (obj[key] && $.trim(obj[key]) == '')
				//     obj[key] = [];
			});
		},
		directExport: function(params) {
			/**
			 *  params对象包含如下参数
			 *  grid: 要导出的表格对象
			 *  exportFileName: 导出的文件名
			 *  excludeFields: 导出要排除的列名
			 *  getCusomizedData: 自定义的取值方法
			 *  handleGridColumns: 处理列数据
			 */
			var gridColumns = $.extend([], params.grid.getColumns());

			if (params.handleGridColumns && typeof params.handleGridColumns === 'function') {
				gridColumns = params.handleGridColumns(gridColumns);
			}

			gridColumns[0].name === '#checkcolumn#' ?
				gridColumns.splice(0, 2) : gridColumns.splice(0, 1);
			// 排除无用列
			if (params.excludeFields && params.excludeFields.length > 0) {
				gridColumns = gridColumns.filter(function(item) {
					return $.inArray(item.field, params.excludeFields) === -1;
				});
			}

			var dataTypes = [];
			var sortField = []; //自定义列可能导致位置改变
			var headerNames = gridColumns.map(function(item) {
				sortField.push(item.displayField || item.field); //导出优先displayField 字段
				if (item.authority === false)
					item.dataType = 'string'; //无权限查看
				let dataType = '0';
				switch (item.sortType || item.dataType) {
					case 'string':
						dataType = '0';
						break;
					case 'boolean':
						dataType = '1';
						break;
					case 'int':
						dataType = '2';
						break;
					case 'float':
					case 'currency':
						dataType = '3';
						break;
					case 'date':
						dataType = '4';
						break;
					case 'image':
						dataType = '8';
						break;
				}
				dataTypes.push(dataType);
				return (item.exportHeader || item.header).replace(/<\/?.+?\/?>/g, ''); //去掉表头i 标签
			});

			var arrData = params.getCusomizedData(sortField);
			var p = {};
			p.headers = {
				'headerNames': headerNames,
				'dataTypes': dataTypes
			};
			p.datas = {
				'datas': arrData
			};
			p.exportFileName = params.exportFileName;
			if (params.plaintext === true) {
				p.plaintext = params.plaintext;
			}
			jkUtils.jkAjax({
				url: '/jkyun/excel-service/manager/directExcelExport',
				type: 'post',
				data: p,
			}).done((data, status, msg, result) => {
				jkUtils.downloadFile(result.result.data);
			}).always(() => {
				mini.unmask(document.body);
			});
		},
		/**
		 * 客户端导出 （已废弃）
		 * @param   {Object}  config
		 * url: {String} 必传 客户端导出前，业务组初始化处理接口
		 * ajaxType：{String} 接口请求类型
		 * headersJson {String} 必传 导出表头
		 * conditionJson {String} 必传 导出时的筛选条件
		 * excelType {Number} 必传 excel导出类型，由业务组提供
		 * multiSheet {Number} 1:多sheet导出。0:单sheet导出
		 * totalFlag {Number} 1:全量导出。0:当前页导出/勾选行导出
		 */
		clientExcelExport: function(config) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			};
			// if (!jkUtils.jkIsFunction(window.jk_exportExcelOperate)) {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }
			// if (!jkUtils.jkIsObject(config)) return;
			// if (window.jk_exportExcelIsRunning() === '1') {
			// 	jkUtils.showTip({
			// 		content: '当前有导出任务正在进行中，请稍后再试'
			// 	});
			// 	return;
			// }
			let {
				headersJson,
				conditionJson,
				excelType,
				multiSheet = 0,
				totalFlag = 0,
				url,
				ajaxType = 'post'
			} = config;
			if (!excelType || !url || !headersJson || !conditionJson) return;
			mini.mask({
				el: document.body,
				cls: 'mini-mask-loading',
				html: jkUtils.jki18n('base_msg_100017', '任务进行中...', 'base')
			});
			jkUtils.jkAjax({
				url,
				type: ajaxType,
				data: {
					headersJson,
					conditionJson,
					excelType,
					multiSheet,
					totalFlag
				}
			}).done((result) => {
				// window.jk_exportExcelOperate((result.data && result.data.initMsg) || ''); //开启导出新任务
				mini.unmask(document.body);
				// 提示
				let taskTitle = '';
				try {
					let initMsg = JSON.parse(result.data.initMsg);
					initMsg.taskTitle && (taskTitle = '“' + initMsg.taskTitle + '”');
				} catch (e) {}
				$(topWindow.document).find('footer .export-task-pop #taskTitle').text(taskTitle);
				$(topWindow.document).find('footer .export-task-pop').show();
				setTimeout(() => {
					$(topWindow.document).find('footer .export-task-pop').hide();
				}, 3 * 1000)
			}).always(() => {
				mini.unmask(document.body);
			});
		},
		/**
		 * 客户端导出
		 * @param   {Object}  config
		 * url: {String} 必传 客户端导出前，业务组初始化处理接口
		 * ajaxType：{String} 接口请求类型
		 * ajaxParams {Object} 接口入参
		 *  headersJson {String} 导出表头
		 *  conditionJson {String} 导出时的筛选条件
		 *  excelType {Number} excel导出类型，由业务组提供
		 *  multiSheet {Number} 1:多sheet导出。0:单sheet导出
		 *  totalFlag {Number} 1:全量导出。0:当前页导出/勾选行导出
		 *  ......
		 */
		cliExcelExport: function(config) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			};
			// if (!jkUtils.jkIsFunction(window.jk_exportExcelOperate)) {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }
			// if (!jkUtils.jkIsObject(config)) return;
			// if (window.jk_exportExcelIsRunning() === '1') {
			// 	jkUtils.showTip({
			// 		content: '当前有导出任务正在进行中，请稍后再试'
			// 	});
			// 	return;
			// }
			let {
				url,
				ajaxType = 'post',
				ajaxParams
			} = config;
			mini.mask({
				el: document.body,
				cls: 'mini-mask-loading',
				html: jkUtils.jki18n('base_msg_100017', '任务进行中...', 'base')
			});
			jkUtils.jkAjax({
				url,
				type: ajaxType,
				data: ajaxParams
			}).done((result) => {
				// window.jk_exportExcelOperate((result.data && result.data.initMsg) || ''); //开启导出新任务
				mini.unmask(document.body);
				// 提示
				let taskTitle = '';
				try {
					let initMsg = JSON.parse(result.data.initMsg);
					initMsg.taskTitle && (taskTitle = '“' + initMsg.taskTitle + '”');
				} catch (e) {}
				$(topWindow.document).find('footer .export-task-pop #taskTitle').text(taskTitle);
				$(topWindow.document).find('footer .export-task-pop #taskStatus').text('导出任务进行中...');
				$(topWindow.document).find('footer .export-task-pop #client_task_button').hide();
				$(topWindow.document).find(
					'footer .export-task-pop .task-loading, footer .export-task-pop .enterClientExport, footer .export-task-pop'
				).show();
			}).always(() => {
				mini.unmask(document.body);
			});
		}
	},
	//设置 js-xlsx 插件 execl文件样式、单元格值
	xlsxExportFunConfig: {
		//初始化
		init: function(workBook, sheetName, cell) {
			if (!workBook.Sheets[sheetName][cell].s) {
				workBook.Sheets[sheetName][cell].s = {};
			}
		},

		init1: function(workBook, sheetName, cell, attr) {
			this.init(workBook, sheetName, cell);
			if (!workBook.Sheets[sheetName][cell].s[attr]) {
				workBook.Sheets[sheetName][cell].s[attr] = {};
			}
		},

		init2: function(workBook, sheetName, cell, attr1, attr2) {
			this.init(workBook, sheetName, cell);
			this.init1(workBook, sheetName, cell, attr1);
			if (!workBook.Sheets[sheetName][cell].s[attr1][attr2]) {
				workBook.Sheets[sheetName][cell].s[attr1][attr2] = {};
			}
		},

		//根据ref的单元格范围新建范围内所有单元格,不存在的单元格置为空值,已存在的不处理
		initAllCell: function(workBook, sheetName) {
			let ref = workBook.Sheets[sheetName]["!ref"].split(":");
			let startCell = ref[0];
			let endCell = ref[1];
			let sc = XLSX.utils.decode_cell(startCell).c;
			let sr = XLSX.utils.decode_cell(startCell).r;
			let ec = XLSX.utils.decode_cell(endCell).c;
			let er = XLSX.utils.decode_cell(endCell).r;
			let isExist;
			for (let c = sc; c <= ec; c++) { //初始化所有单元格
				for (let r = sr; r <= er; r++) {
					let temp = XLSX.utils.encode_cell({
						c: c,
						r: r
					});
					isExist = false;
					for (let cell in workBook.Sheets[sheetName]) {
						if (cell != '!cols' && cell != '!merges' && cell != '!ref') {
							if (temp == cell) {
								isExist = true;
								break;
							}
						}
					}
					if (!isExist) { //单元格不存在则新建单元格
						XLSX.utils.sheet_add_aoa(workBook.Sheets[sheetName], [
							['']
						], {
							origin: temp
						});
					}

				}
			}
		},

		//当前表格最大列数
		getMaxColNum: function(workBook, sheetName) {
			let rows1 = [];
			for (let ever in workBook.Sheets[sheetName]) {
				let num = ever.replace(/[^0-9]/ig, "");
				if ("1" === num) {
					rows1.push(ever);
				}
			}
			return rows1.length;
		},

		//设置每列列宽,单位px cols= [{wpx: 45}, {wpx: 165}, {wpx: 45}, {wpx: 45}]
		setColWidthAll: function(workBook, sheetName, width) {
			let colsNum = this.getMaxColNum(workBook, sheetName); //当前最大列
			//列宽设置 1wch为1英文字符宽度 (统一放大一下宽度)
			let widths = [];
			for (let i = 0; i < colsNum; i++) {
				widths.push({
					wch: width
				});
			}
			workBook.Sheets[sheetName]["!cols"] = widths;
			return workBook;
		},
		// 所有单元格字体
		setFontTypeAll: function(workBook, sheetName, type) {
			for (let cell in workBook.Sheets[sheetName]) {
				if (cell != '!cols' && cell != '!merges' && cell != '!ref') {
					this.init1(workBook, sheetName, cell, "font");
					workBook.Sheets[sheetName][cell].s.font.name = type;
				}
			}
		},
		//单元格字体大小
		setFontSize: function(workBook, sheetName, cell, size) {
			this.init1(workBook, sheetName, cell, "font");
			workBook.Sheets[sheetName][cell].s.font.sz = size;
			return workBook;
		},

		//字体大小
		setFontSizeAll: function(workBook, sheetName, size) {
			for (let cell in workBook.Sheets[sheetName]) {
				if (cell != '!cols' && cell != '!merges' && cell != '!ref') {
					this.setFontSize(workBook, sheetName, cell, size);
				}
			}
		},

		//使用RGB值设置单元格背景颜色
		setFillFgColorRGB: function(workBook, sheetName, cell, rgb) {
			this.init2(workBook, sheetName, cell, "fill", "fgColor");
			workBook.Sheets[sheetName][cell].s.fill.fgColor.rgb = rgb;
			return workBook;
		},

		//单元格文本水平对齐 "bottom" or "center" or "top"
		setAlignmentHorizontal: function(workBook, sheetName, cell, horizontal) {
			this.init1(workBook, sheetName, cell, "alignment");
			workBook.Sheets[sheetName][cell].s.alignment.horizontal = horizontal;
			return workBook;
		},
		//设置单元格上下左右边框默认样式
		setBorderDefault: function(workBook, sheetName, cell) {
			this.init(workBook, sheetName, cell);
			workBook.Sheets[sheetName][cell].s.border = {
				top: {
					style: 'thin'
				},
				bottom: {
					style: 'thin'
				},
				left: {
					style: 'thin'
				},
				right: {
					style: 'thin'
				}
			};
			return workBook;
		},
		//设置所有单元默认格边框
		setBorderDefaultAll: function(workBook, sheetName) {
			this.initAllCell(workBook, sheetName);
			for (let cell in workBook.Sheets[sheetName]) {
				if (cell != '!cols' && cell != '!merges' && cell != '!ref') {
					this.setBorderDefault(workBook, sheetName, cell);
				}
			}
		},
		// 表格头样式
		setHeaderStyle: function(workBook, sheetName) {
			for (let cell in workBook.Sheets[sheetName]) {
				let num = cell.replace(/[^0-9]/ig, "");
				if ("1" === num) {
					this.setFillFgColorRGB(workBook, sheetName, cell, 'BBBBBB');
					this.setAlignmentHorizontal(workBook, sheetName, cell, 'center');
					this.setFontSize(workBook, sheetName, cell, 12);
				}
			}
		},
		// 设置execl文件默认样式
		setDefaultStyle: function(workBook, sheetName) {
			for (let cell in workBook.Sheets[sheetName]) {
				if (cell != '!cols' && cell != '!merges' && cell != '!ref') {
					// 单元格字体
					this.init1(workBook, sheetName, cell, "font");
					workBook.Sheets[sheetName][cell].s.font.name = 'Arial';
					//字体大小
					this.setFontSize(workBook, sheetName, cell, 10);
					//设置所有单元默认格边框
					this.setBorderDefault(workBook, sheetName, cell);
				}
				// 表格头样式
				let num = cell.replace(/[^0-9]/ig, "");
				if ("1" === num) {
					this.setFillFgColorRGB(workBook, sheetName, cell, 'BBBBBB');
					this.setAlignmentHorizontal(workBook, sheetName, cell, 'center');
					this.setFontSize(workBook, sheetName, cell, 12);
				}
			}
			this.setColWidthAll(workBook, sheetName, 20);
		},
		// 设置某列数据值 col: A B C ......
		setColValue: function(workBook, sheetName, col, getValueFun, field = '') {
			for (let cell in workBook.Sheets[sheetName]) {
				let num = cell.replace(/[^0-9]/ig, "");
				if (cell != '!cols' && cell != '!merges' && cell != '!ref' && cell.substr(0, 1) == col &&
					num !== '1') {
					let val = typeof getValueFun === 'function' ? getValueFun(workBook.Sheets[sheetName][cell]
						.v, field) : ''
					workBook.Sheets[sheetName][cell].v = val;
				}
			}
		}
	},

	/* 客户端方法统一封装 */
	// 浏览器调用客户端方法提示
	showWebTip: function() {
		this.showTip({
			content: "浏览器暂不支持此功能，请到客户端使用完整的功能！",
			state: "warning",
		});
	},
	// 调客户端方法，客户端未更新到提示
	showUpdateTip: function() {
		this.showTip({
			content: "暂不支持此功能，请升级您的客户端！",
			state: "warning",
		});
	},
	// 打开外部链接
	openurl: function(url, name) {
		// 客户端
		if (isInCef) {
			try {
				// window.openurl(url);
			} catch (e) {
				jkUtils.showUpdateTip();
			}
			return;
		}
		// web版
		if (url.indexOf('http') < 0) {
			url = '\/\/' + url;
		}
		// window.open(url, name || '_blank');
	},
	// 获取登录信息
	getLoginInfo: function() {
		// web版获取登录信息
		if (!isInCef) {
			let loginResult;
			try {
				loginResult = JSON.parse(localStorage.getItem("loginResult"));
			} catch (error) {
				loginResult = {};
			}
			return {
				company: localStorage.getItem('company'),
				computer_id: localStorage.getItem('computer_id'),
				member_id: localStorage.getItem('member_id'),
				member_name: localStorage.getItem('member_name'),
				user_id: localStorage.getItem('user_id'),
				user_name: localStorage.getItem('user_name'),
				system_ip: localStorage.getItem('system_ip'),
				forceChangePassword: localStorage.getItem('forceChangePassword'),
				jdHost: localStorage.getItem('jdHost'),
				pddHost: localStorage.getItem('pddHost'),
				ati: this.cookie.getCookie('_ati'),
				loginResult: loginResult,
			};
		}
		// 客户端获取登录信息
		let {
			loginInfo
		} = topWindow
		if (loginInfo && loginInfo.loginResult && JSON.stringify(loginInfo.loginResult) !== "{}")
			return loginInfo
		let res, loginResult;
		try {
			// res = JSON.parse(window.main_get_logininfo());
		} catch (error) {
			return {};
		}

		// 解析loginResult
		try {
			loginResult = JSON.parse(res.loginResult);
			res.loginResult = loginResult;

		} catch (error) {
			res.loginResult = {};
			return res;
		}
		// 兼容登录v5接口，把新的数据结构还原成旧结构
		if (Object.prototype.toString.call(loginResult.result) === "[object Object]") {
			try {
				let data = res.loginResult.result.data;
				loginResult = {};
				for (let key in data) {
					loginResult = $.extend(loginResult, data[key] || {});
				}
				res.loginResult = loginResult;
			} catch (error) {
				res.loginResult = {};
				return res;
			}
		}

		return res;
	},
	// 播放音频声音
	playAudio: function(key, type) {
		// if (!isInCef || (typeof window.jk_playSoundByPath !== "function")) {
		// 	return;
		// }

		var types = ["system", "message", "business"];
		var localConfigs = topWindow.localConfigs;

		if (types.indexOf(type) < 0) {
			type = "system";
		}
		if (!localConfigs) return;

		// 客户端总声音关闭，不播放声音
		if (localConfigs.voiceControl == 1) return;

		// 声音分类
		var voiceType = localConfigs[type + "Voice"];
		if (!voiceType) return;

		// 声音配置key
		var voiceConfig = voiceType.find(v => v.key == key);
		if (!voiceConfig) return;

		// if (voiceConfig.state == 1) {
		// 	window.jk_playSoundByPath(voiceConfig.path);
		// }
	},
	// 文字转语音播放 vol:音量，取值0-9，默认5；per: 男声女声  0：女声，1：男声
	readWord: function(text, vol = 5, per = 0) {
		// if (!isInCef || (typeof window.play_text !== "function")) {
		// 	try {
		// 		var speech = new SpeechSynthesisUtterance();
		// 		speech.text = text;
		// 		speech.volume = 1; // 语音频道
		// 		speech.rate = 1; // 语音速度
		// 		speech.pitch = 1; // 语音高低
		// 		window.speechSynthesis.speak(speech);
		// 	} catch (error) {}
		// 	return;
		// }
		//
		// window.play_text(text, JSON.stringify({
		// 	vol,
		// 	per
		// }));
	},
	// 写日志到壳下的日志文件中
	writeLogToShell: function(content) {
		// if (!isInCef || (typeof window.web_writelog !== "function")) {
		// 	return;
		// }
		// window.web_writelog(content);
	},
	// 将内容写到指定的文件中
	writeIntoFile(fileName, content) {
		// if (!isInCef || (typeof window.jk_useroperation !== "function")) {
		// 	return;
		// }
		// window.jk_useroperation(fileName, content);
	},
	// 读取指定文件中的文本内容
	readFileContent(fileName) {
		// if (!isInCef || typeof window.jk_readfilecontent !== "function") {
		// 	return;
		// }
		// return window.jk_readfilecontent(fileName);
	},
	// 获取Ati参数
	getAti: function() {
		if (!isInCef) {
			return this.cookie.getCookie("_ati");
		}
		var res;
		try {
			// res = JSON.parse(window.get_token_ati()).ati;
		} catch (error) {
			res = "";
		}
		return res;
	},
	// 获取电脑id
	getComputerId: function() {
		var res;
		if (!isInCef) {
			res = localStorage.getItem("computer_id");
			res = res ? res : "0";
			return res;
		}
		try {
			// res = window.jk_get_computer_id();
		} catch (error) {
			res = "";
		}
		res = res ? res : "0";
		return res;
	},

	// 硬件相关 pos有单独封装
	hardwareClient: {
		// 硬件类型
		deviceType: {
			ICCard: 'ICCard',
			CashBox: 'CashBox',
			Weight: 'Weight',
			ScreenShow: 'ScreenShow'
		},
		// 注册硬件
		regHardwareProxy: function(params, callback) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			}
			// if (typeof window.jk_hardwarework_start !== "function") {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }
			//
			// if (!window.hardwareProxy) window.hardwareProxy = [];
			//
			// var activeTab = topWindow.jkMainTabs.getActiveTab();
			if (activeTab)
				params.tabId = activeTab.name;
			if (!params.tabId) return;
			if (params.FrameName) params.tabId += '$' + params.FrameName;

			// var proxy = window.hardwareProxy.find(function(item) {
			// 	return item.tabId == params.tabId && item.DeviceName == params.DeviceName;
			// });
			// if (!proxy)
			// 	window.hardwareProxy.push($.extend({}, params, {
			// 		callback: callback
			// 	}));
			// else
			// 	$.extend(proxy, params, {
			// 		callback: callback
			// 	});

			// 调用mainframe方法去注册该组件
			topWindow.jkMainFramePage.jkHardware.regRequest(params);
		},
		// 注销硬件
		unregHardwareProxy: function(params) {
			params.tabId = topWindow.jkMainTabs.getActiveTab().name;
			if (params.FrameName) params.tabId += '$' + params.FrameName;
			topWindow.jkMainFramePage.jkHardware.unregRequestByTab(params.tabId);
		}
	},
	// 称重相关
	weigh: {
		weighTimer: null,
		lastValue: null,
		// 获取设备端口号
		getSysCOMs: function() {
			if (!isInCef) {
				jkUtils.showWebTip();
				return "";
			}
			// if (typeof window.get_system_coms !== "function") {
			// 	jkUtils.showUpdateTip();
			// 	return "";
			// }
			//
			// return window.get_system_coms();
			return "";
		},
	},
	// 打印相关
	printer: {
		// 获取打印机
		getAll: function() {
			// if (!isInCef || (typeof window.print_get_all !== "function")) {
			// 	return [];
			// }
			// var res;
			// try {
			// 	res = JSON.parse(window.print_get_all());
			// } catch (error) {
			// 	res = [];
			// }
			return {};
		},
		// 打印
		printDesign: function(className, moduleName, paramValue) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			}
			// if (typeof window.print_design !== "function") {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }

			var res, param = {};
			param.ClassName = className;
			param.ModuleName = moduleName;
			param.ParamValue = paramValue;
			// try {
			// 	res = window.print_design(JSON.stringify(param));
			// } catch (error) {}

			return res;
		},
		// 生成打印报告
		printReport: function(className, moduleName, paramValue) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			}
			// if (typeof window.print_report !== "function") {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }

			var param = {
				ClassName: className,
				ModuleName: moduleName,
			};
			if (Array.isArray(paramValue)) {
				param.ParamValue = JSON.stringify(paramValue);
			} else if (typeof paramValue === 'object') {
				Object.keys(paramValue).forEach(function(key) {
					if (typeof paramValue[key] === 'object') {
						paramValue[key] = JSON.stringify(paramValue[key]);
					}
				});
				param.ParamValue = decodeURIComponent($.param(paramValue));
			} else {
				param.ParamValue = paramValue;
			}
			// return window.print_report(JSON.stringify(param));
			return "";
		},
		getParamValue: function(data) {
			var param = {};
			if (Array.isArray(data)) {
				param = JSON.stringify(data);
			} else if (typeof data === 'object') {
				Object.keys(data).forEach(function(key) {
					if (typeof data[key] === 'object')
						param[key] = JSON.stringify(data[key]);
					else
						param[key] = data[key];
				});
				param = decodeURIComponent($.param(param));
			} else
				param = data;

			return param;
		},
		// 打开打印弹窗
		openPrinterSelector: function(options) {
			mini.open({
				// targetWindow: window,
				url: '/common/printer/printer_selector.html',
				title: jkUtils.jki18n('com_print', '打印', 'common') + ' ' + options.className,
				width: 400,
				height: 180,
				showModal: true,
				allowResize: false,
				onload: function() {},
				ondestroy: function(action) {
					if (action == 'ok') {
						var iframe = this.getIFrameEl();
						var printer = iframe.contentWindow.getData();
						JKPrint.jkprint_deliver(options.paramValue.value, options.paramValue.type,
							false, printer);
					}
				}
			});
		},
	},
	// 配置对象，里面有打印相关的配置
	localConfig: {
		handleCheckBoxStatusChanged(mainCkb, ctrArr) {
			var enabled = mainCkb.getValue() > 0;
			ctrArr.forEach(function(control) {
				switch (control.type) {
					case 'checkbox':
						control.set({
							enabled: enabled
						});
						if (!enabled)
							control.set({
								value: mainCkb.getValue()
							});
						break;
					case 'combobox':
						if (!enabled) {
							control.set({
								enabled: enabled
							});
							control.setValue();
						}
						break;
				}
			});
		},
		// 获取打印参数
		getByModule: function(moduleId) {
			// if (!isInCef || (typeof window.print_get_one !== "function")) {
			// 	return "";
			// }
			// return window.print_get_one(moduleId);
		},
		// 设置打印参数
		setForModule: function(moduleId, value) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			}
			// if (typeof window.print_set_one !== "function") {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }
			// return window.print_set_one(moduleId, value);
		},
		// 删除打印参数
		deleteByModule: function(moduleId) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			}
			// if (typeof window.print_del_one !== "function") {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }
			// window.print_del_one(moduleId);
		},
		// 获取批量打印参数
		batchGetConfig: function(keyArray) {
			// if (!isInCef || (typeof window.print_get_one !== "function")) {
			// 	return {};
			// }
			// var data = {};
			// $.each(keyArray, function(i, key) {
			// 	data[key] = window.print_get_one(key);
			// });
			return data;
		},
		// 设置批量打印参数
		batchSetConfig: function(data) {
			if (!isInCef) {
				jkUtils.showWebTip();
				return;
			}
			// if (typeof window.print_set_batch !== "function") {
			// 	jkUtils.showUpdateTip();
			// 	return;
			// }

			mini.mask({
				el: document.body,
				cls: 'mini-mask-loading',
				html: jkUtils.jki18n('base_msg_100013', '保存中...', 'base')
			});

			var json = [];
			for (var key in data) {
				json.push({
					key: key,
					keyvalue: data[key]
				});
			}
			// window.print_set_batch(JSON.stringify(json));

			mini.unmask();
		},
	},
	/* 客户端方法统一封装 */

	// 打开批次选择器
	openGoodBatchSelector(skuId, callback) {
		mini.open({
			targetWindow: window,
			url: '/common/goods/batch_selector.html',
			title: jkUtils.jki18n('base_msg_100018', '请选择批次', 'base'),
			width: 600,
			height: 550,
			showModal: true,
			allowResize: false,
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.setData({
					skuId: skuId
				});
			},
			ondestroy: function(action) {
				if (action === 'ok') {
					if (typeof callback === "function") {
						var iframe = this.getIFrameEl();
						var data = iframe.contentWindow.getData();
						callback(data);
					}
				}
			}
		});
	},

	/*
	打开配置字段，显示，顺序
	optins={
	         title: '设置查询条件', //标题
	        width: 450,  //宽度
	        height: 400, //高度
	        path: options.path, //相对路径 ji_mini
	        actionName: actionNameQryConfig  //actionName
	}
	*/
	openFieldConfig: function(options, queryKeyArr, func) {

		options = $.extend({
			title: jkUtils.jki18n('base_msg_100019', '设置查询条件', 'base'),
			width: 800,
			height: 600,
			ismulti: true,
			issort: true,
			isedit: false,
			fieldArr: [].concat(queryKeyArr),
			data: []
		}, options);

		if (!options.actionName) throw new Error('options.actionName不能为空！');

		mini.open({
			targetWindow: window,
			url: String.format('{0}/selectCondition.html', options.path),
			title: options.title,
			width: options.width,
			height: options.height,
			showHeader: true,
			showModel: true,
			allowResize: false,
			showCloseButton: true,
			isEdit: false,
			onload: function() {
				var data = {};
				data.allFields = [].concat(queryKeyArr);
				var fieldsConfig = jkUtils.jkGetCustomConfig(options.actionName);

				if (Object.prototype.toString.call(fieldsConfig) !== '[object Object]') {
					fieldsConfig = {
						selectedFields: [].concat(queryKeyArr)
					};
				}
				data.selectedFields = fieldsConfig.selectedFields;
				data.isEdit = options.isEdit;
				var iframe = this.getIFrameEl();
				iframe.contentWindow.selectFieldsPage.setData(data);
			},
			ondestroy: function(message) {
				if (message === 'ok') {
					try {
						var iframe = this.getIFrameEl();
						var data = iframe.contentWindow.selectFieldsPage.getData();
						data.selectedFields = data.selectedFields.filter(f => {
							return options.fieldArr.find(qry => {
								return qry.field === f.field;
							});
						}).map(f => {
							var field = options.fieldArr.find(qry => {
								return qry.field === f.field;
							});
							field.edit = f.edit;
							return field;
						});

						var saveSetting = jkUtils.jkSaveCCSConfig({
							actionName: options.actionName,
							configs: data,
							callback: function() {
								if (func && typeof func === 'function') {
									func(data);
								}
							},
						});

						jkUtils.jkDoWaitting(saveSetting, {
							html: jkUtils.jki18n('base_msg_100014', '正在保存...', 'base')
						});
					} catch (e) {
						console.info(e);
					}

				}
			}
		});
	},
	/**
	 * 客户选择器，配置多选返回Array ，单选返回 object
	 * @param {Object} options  配置多选单选
	 * @param {Function} func  回调函数
	 */
	openCustomerSelector: function(options, func) {
		return mini.open({
			targetWindow: options.window || window,
			url: '/crm/dist/customer_sel.html',
			title: jkUtils.jki18n('base_msg_100020', '选择客户', 'base'),
			width: '96%',
			height: '96%',
			showModel: true,
			allowResize: false,
			showHeader: true,
			showCloseButton: true,
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.customerSelectorPage && iframe.contentWindow
					.customerSelectorPage.setData(options);
			},
			ondestroy: function(message) {
				if (message === 'ok') {
					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.customerSelectorPage.getData();
					if (func && typeof func === 'function')
						func(data);
				}
			}
		});
	},
	/*
	公共单据驳回关闭等选择组件, options参数必填
	options = {
	        title: '标题',  // 标题
	        dictValue: 'close', // 数据字典值
	        ajaxType:'get',  //请求方式
	        url: '/jkyun/erp',  // 请求地址
	        params : {}  //请求参数
	        text: 'logMsgCn' //原因参数接口字段
	*/
	openReasonSelector: function(options, func) {
		mini.open({
			targetWindow: window,
			url: '/common/reason/reason.html',
			title: options.title,
			width: 405,
			height: 350,
			showModel: true,
			allowResize: false,
			showHeader: true,
			showCloseButton: true,
			onload: function() {
				var iframe = this.getIFrameEl();
				// iframe.contentWindow.reasonPage.setData(options);
			},
			ondestroy: function(message) {
				if (message === 'ok') {
					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.reasonPage.getData();
					if (func && typeof func === 'function')
						func(data);
				}
			}
		});
	},

	// 关于标记的操作方法
	flagCfg: {
		getRowColorCfg: function(flagList, row, sysFlagCallback, flagField, cusFlagCallback) {
			var sysColor = null,
				cusColor = null,
				rowStyle = '';

			if (sysFlagCallback) {
				sysColor = sysFlagCallback(flagList, row);
				if (sysColor) {
					if (sysColor.flagBgColor)
						rowStyle += 'background-color:' + sysColor.flagBgColor + ';';
					if (sysColor.flagFontColor)
						rowStyle += 'color:' + sysColor.flagFontColor + ';';
				}
			}

			var flagIds = row[flagField ? flagField : 'flagIds'];

			if (flagIds) {
				if (cusFlagCallback)
					cusColor = cusFlagCallback(flagList, flagIds, row);
				else {
					var flagIdArr = Object.prototype.toString.call(flagIds) == '[object Array]' ? flagIds :
						flagIds.split(',');
					var cusColorArr = $.grep(flagList, function(item) {
						return $.inArray(item.flagId, flagIdArr) >= 0 && item.isChangeRowdata > 0;
					});
					if (cusColorArr.length > 0) {
						var cusSortArr = cusColorArr.sort(function(a, b) {
							return b.flagPriority - a.flagPriority;
						});
						cusColor = cusSortArr[0]; //取优先级最大的值 by chenyanping
					}
				}

				if (cusColor) {
					if (cusColor.flagBgColor)
						rowStyle += 'background-color:' + cusColor.flagBgColor + ';';
					if (cusColor.flagFontColor)
						rowStyle += 'color:' + cusColor.flagFontColor + ';';
				}
			}

			return rowStyle;
		},

		generateFlagStyle: function(flagList) {
			var styleHtml = '<style type="text/css">';
			flagList.forEach(function(item) {
				styleHtml += '.flagClass' + item.flagId + ' {background-color:' + item.flagBgColor +
					'; color: ' + item.flagFontColor + '} ';
			});
			styleHtml += '</style>';
			$('title').after(styleHtml);
		},

		getRowColorClass: function(flagList, row, sysFlagCallback, flagField, cusFlagCallback) {
			var cusColor = null,
				rowClass = '';

			if (sysFlagCallback)
				rowClass = sysFlagCallback(flagList, row);

			var flagIds = row[flagField ? flagField : 'flagIds'];

			if (flagIds) {
				if (cusFlagCallback)
					rowClass = cusFlagCallback(flagList, flagIds, row);
				else {
					// huangjp 2019-10-06
					// 优化标记修改行颜色的性能，找出需要变色的标记排序并缓存，避免重复查找和重复排序
					var flagIdArr = Object.prototype.toString.call(flagIds) == '[object Array]' ? flagIds :
						flagIds.split(','),
						cusColorArr = [];
					if (this.changeRowArr === undefined) {
						this.changeRowArr = flagList.filter(function(item) {
							return item.isChangeRowdata > 0;
						});
						this.changeRowArr = this.changeRowArr.sort(function(a, b) {
							return b.flagPriority - a.flagPriority;
						});
					}
					if (this.changeRowArr.length > 0) {
						cusColorArr = this.changeRowArr.filter(function(item) {
							return $.inArray(item.flagId, flagIdArr) >= 0;
						});
					}
					if (cusColorArr.length > 0) {
						cusColor = cusColorArr[0]; //取优先级最大的值 by chenyanping
					}
				}

				if (cusColor)
					rowClass = 'flagClass' + cusColor.flagId;
			}

			return rowClass;
		},

		getAllClass: function(flagList) {
			var classArr = [];
			if (flagList) {
				var classArr = $.grep(flagList, function(item) {
					return item.isChangeRowdata > 0;
				});
				classArr = classArr.map(color => {
					return 'flagClass' + color.flagId;
				});
			}
			return classArr;
		},

		renderFlagCell: function(flagList, flags) {
			var result = '';
			if (flags) {
				var flagIdArr = [];
				if (flags && Object.prototype.toString.call(flags) == '[object Array]')
					flagIdArr = flags;
				else
					flagIdArr = flags.split(',');
				// edit by panluhua 2019/08/15 原因：之前的方法导致列表中的标记排序和左侧不一致
				var mappedFlags = flagIdArr.reduce(function(prev, cur) {
					var matchFlagList = Array.isArray(flagList) ? flagList.filter(_ => _.flagId ==
						cur) : [];
					if (matchFlagList && _.isArray(matchFlagList) && matchFlagList.length > 0) {
						return prev.concat(matchFlagList);
					} else {
						return prev;
					}
				}, []);

				result = mappedFlags.reduce(function(prev, curr) {
					var styleStr = '';
					if (curr.flagBgColor)
						styleStr += 'background-color:' + curr.flagBgColor + ';';
					if (curr.flagFontColor)
						styleStr += 'color:' + curr.flagFontColor + ';';

					return prev + (prev ? '&nbsp' : '') +
						`<a title="${(curr.flagDesc || "")}" class="flag-btn" style="${styleStr}">${curr.flagName}</a>`;
				}, '');
			}

			return result;
		},

		// 更新行的背景色
		updateRowBg: function(grid, row, flagList, flagIds, flagField) {
			var rowClass = this.getRowColorClass(flagList, row, '', flagField);
			flagList.forEach(function(item) {
				grid.removeRowCls(row, 'flagClass' + item.flagId);
			});
			grid.addRowCls(row, rowClass);
		}
	},

	date: {
		dateCalc: function(date, num, unit, action) {
			var d = typeof date === 'string' ? new Date(date) : date;
			switch (unit) {
				case 'hour':
					if (action === 'add')
						d.setHours(d.getHours() + num);
					else
						d.setHours(d.getHours() - num);
					break;
				case 'day':
					if (action === 'add')
						d.setDate(d.getDate() + num);
					else
						d.setDate(d.getDate() - num);
					break;
				case 'week':
					if (action === 'add')
						d.setDate(d.getDate() + num * 7);
					else
						d.setDate(d.getDate() - num * 7);
					break;
				case 'month':
					if (action === 'add')
						d.setMonth(d.getMonth() + num);
					else
						d.setMonth(d.getMonth() - num);
					break;
				case 'year':
					if (action === 'add')
						d.setFullYear(d.getFullYear() + num);
					else
						d.setFullYear(d.getFullYear() - num);
					break;
			}
			return d;
		},

		dateAdd: function(date, num, unit) {
			return this.dateCalc(date, num, unit, 'add');
		},

		dateMinus: function(date, num, unit) {
			return this.dateCalc(date, num, unit);
		},

		formatTimeStamp: function(timestamp, format) {
			return jkUtils.formatTimeStamp(timestamp, format);
		},

		formatTimeStampV2: function(timestamp) {
			return jkUtils.formatTimeStampV2(timestamp);
		},
	},

	// 默认Datetime,  提供Date(yyyy-MM-dd)和DateTime(yyyy-MM-dd HH:mm:ss)
	formatTimeStamp(timeStamp, format) {
		// var date = timeStamp ? new Date(window.isNaN(Number(timeStamp)) ? timeStamp.replace(/-/g, "/") : Number(
		// 		timeStamp)) : null,
		// 	result = '';
		if (!format)
			format = 'DateTime';
		switch (format) {
			case 'Date':
				result = mini.formatDate(date, 'yyyy-MM-dd');
				break;
			case 'DateTime':
				result = mini.formatDate(date, 'yyyy-MM-dd HH:mm:ss');
				break;
			default:
				result = mini.formatDate(date, format);
				break;
		}
		return result;
	},
	formatTimeStampV2(timeStamp, type = "DateTime") {
		if (typeof timeStamp === "string") timeStamp = timeStamp.replace(/-/g, ",");

		// var result = "",
		// 	date = timeStamp ? new Date(window.isNaN(Number(timeStamp)) ? timeStamp : Number(timeStamp)) : null;

		switch (type) {
			case 'Date':
				result = mini.formatDate(date, 'yyyy-MM-dd');
				break;
			case 'DateTime':
				result = mini.formatDate(date, 'yyyy-MM-dd HH:mm:ss');
				break;
			default:
				result = mini.formatDate(date, type);
				break;
		}
		return result;
	},

	// 根据相差毫秒数，计算时间字符串
	getTimeString(time) {
		if (time > 0) {
			let d = 24 * 60 * 60 * 1000,
				h = 60 * 60 * 1000,
				m = 60 * 1000;

			let day = Math.floor(time / d);
			let hour = Math.floor((time - day * d) / h);
			let minute = Math.ceil((time - day * d - hour * h) / m);
			return (day ? day + '天' : '') + (hour ? hour + '小时' : '') + minute + '分钟';

		} else {
			return null;
		}
	},

	// 获得时间描述或转换成时间格式
	getDateDesc(timeStamp, format) {
		var result = '';
		if (timeStamp) {
			var currentTime = new Date().getTime();
			var i = Math.ceil((currentTime - timeStamp) / 86400000);
			switch (i) {
				case 1:
					result = jkUtils.jki18n('com_today', '今天', 'common');
					break;
				case 2:
					result = jkUtils.jki18n('com_yesterday', '昨天', 'common');
					break;
				case 3:
					result = jkUtils.jki18n('base_in_three_days', '三天内', 'base');
					break;
				default:
					result = this.formatTimeStamp(timeStamp, format);
					break;
			}
		}
		return result;
	},

	// JSON解析成js对象，防止精度丢失
	jsonParse(jsonStr) {
		let jsonData = null;
		try {
			jsonStr = jsonStr.replace(/:\s*(\d{15,})\s*(,?)/g, ': "$1" $2')
			jsonData = JSON.parse(jsonStr)
		} catch (err) {
			throw Error(err);
		}
		return jsonData
	},

	// 从本地存储获取枚举
	getEnumByKey(key) {
		return JSON.parse(localStorage.getItem(key));
	},
	// add by zhongbin 2018/4/13 start
	/**
	 * @param {Array} idList
	 * @param {String} moduleCode
	 * @param {String} sysCode
	 */
	showInvoicePop(idList, moduleCode, sysCode) {
		var config = {
			idList: idList || [],
			moduleCode: moduleCode || '',
			sysCode: sysCode || ''
		};
		mini.open({
			// targetWindow: window,
			width: '340',
			height: '300',
			title: jkUtils.jki18n('base_msg_100021', '业务单据', 'base'),
			url: './common/order/invoice_pop.html',
			allowResize: false,
			showCloseButton: true,
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.invoicePop.setData(config);
			}
		});
	},
	//
	regSubTabInteract(config) {
		var timmer = null;

		function loadTabData(tabName, config, row, flag) {
			const action = config.actions.find(item => item.name == tabName);
			const subTab = config.subTab;
			if (!action) return;
			let findTab;
			if (subTab && Array.isArray(subTab.tabs)) {
				findTab = subTab.tabs.find(item => item.name === tabName);
				findTab && (action.isLoaded = findTab.isLoaded);
			}
			if (!action.isLoaded) {
				const bindGrid = flag ? typeof action.loadEmptyData === 'function' && action.loadEmptyData() :
					action.loadData(row);
				action.isLoaded = 1;
				bindGridEvent(action, bindGrid);
				findTab && (findTab.isLoaded = 1);
			}
		}

		//如果主表上没有数据，则取消子表的刷新功能
		const bindGridEvent = (action, bindGrid) => {
			if (action.isBindedEvent) return;
			try {
				if (bindGrid instanceof JKControl.JKDataGrid) {
					bindGrid.on('beforeload', function(e) {
						if (!config.mainGrid.jkGetSelected())
							e.cancel = true;
					});
					action.isBindedEvent = true;
					return;
				}
			} catch (error) {
				console.log(error);
			}
			var tab = config.subTab.getTab(action.name);
			var $grid = $(config.subTab.getTabBodyEl(tab)).find('.jk-mini-base-datagrid');
			if ($grid.length > 0) {
				var gridId = $grid.eq(0).prop('id');
				const subGrid = mini.get(gridId);
				if (!subGrid) return;
				action.grid = subGrid;
				subGrid.on('beforeload', function(e) {
					if (!config.mainGrid.jkGetSelected())
						e.cancel = true;
				});
				action.isBindedEvent = true;
			}
		}

		function initLoadState(config) {
			const subTab = config.subTab;
			if (subTab && Array.isArray(subTab.tabs)) {
				subTab.tabs.forEach(item => item.isLoaded = 0);
			}
			config.actions = config.actions.map(function(item) {
				item.isLoaded = 0;
				return item;
			});
		}

		initLoadState(config);

		// isRebindSCEvent 是否重新绑定selectionchanged事件
		if (config.isRebindSCEvent)
			config.mainGrid.un('selectionchanged');

		config.mainGrid.on('selectionchanged', function(e) {
			if (timmer)
				// window.clearTimeout(timmer);

			timmer = setTimeout(function() {
				//config.mainGrid.mask(); huangjp 2019-10-06 暂时移除主表的遮罩层，避免让用户感到卡顿
				initLoadState(config);

				if (config.mainGridChanged)
					config.mainGridChanged(e.selected);

				// var activeTab = typeof config.subTab.getActiveTab == 'function' ? config.subTab.getActiveTab() : config.subTab;
				var activeTab = config.subTab.getActiveTab();
				if (e.selected && activeTab) {
					// 清除子表的表头查询
					var actionItem = config.actions.find(function(item) {
						return activeTab.name == item.name;
					});
					if (actionItem && actionItem.grid)
						actionItem.grid.clearHeadFilterCondition();
					// 加载子表数据
					loadTabData(activeTab.name, config, e.selected);
				}

				// window.clearTimeout(timmer);
				// config.mainGrid.unmask(); huangjp 2019-10-06 暂时移除主表的遮罩层，避免让用户感到卡顿
			}, 400);
		});

		config.subTab.on('activechanged', function(e) {
			var selected = config.mainGrid.getSelected();
			if (selected) {
				loadTabData(e.name, config, selected);
				return;
			}
			// 假如没有选中行，触发业务组传入的无选中数据的回调函数
			loadTabData(e.name, config, selected, true);
		});
	},

	getImgUrl(imgObj, size) {
		var imgUrl = '';
		if (imgObj) {
			if (imgObj.indexOf('{') > -1) {
				try {
					var imgDict = JSON.parse(imgObj);
					switch (size) {
						case 'big':
							imgUrl = imgDict['pic0x0'];
							break;
						case 'middle':
							imgUrl = imgDict['pic400x400'];
							break;
						default:
							imgUrl = imgDict['pic50x50'];
							break;
					}
				} catch (err) {
					imgUrl = imgObj;
				}
			} else
				imgUrl = ['[]', 'null', 'undefined'].includes(imgObj) ? '/styles/img/default.png' : imgObj;
		} else {
			switch (size) {
				case 'big':
					imgUrl = '/styles/img/default_max.png';
					break;
				case 'middle':
					imgUrl = '/styles/img/default_middle.png';
					break;
				case 'min':
					imgUrl = '/styles/img/default_min.png';
					break;
				default:
					imgUrl = '/styles/img/default.png';
					break;
			}
		}

		return imgUrl;
	},

	popupLayer: {
		pops: [],

		init: function(eleId, relEleId, config) {
			var _this = this;
			this.pops.push(eleId);

			var $ele = $('#' + eleId),
				$relEle = $('#' + relEleId);

			$relEle.on('click', function(e) {
				$.each(_this.pops, function(i, item) {
					_this.hidePopup(item);
				});
				e.stopPropagation();
				var top = $(this).offset().top + (config && config.top != null ? config.top : 20);
				var left = $(this).offset().left + (config && config.left != null ? config.left : 0);
				$ele.css({
					'top': top,
					'left': left
				});
				if (config && config.callback)
					config.callback();
				$ele.show();
			});

			$ele.on('click', function(e) {
				e.stopPropagation();
			});

			$(document).on('click', function() {
				$ele.hide();
			});
		},

		hidePopup: function(eleId) {
			$('#' + eleId).hide();
		}
	},

	// 获取邮资
	calcPostage(state, city, district, street, logisticId, warehouseId, weight, orderId, callback) {
		var data = [{
			state: state,
			city: city,
			district: district,
			town: street,
			logisticId: logisticId,
			warehouseId: warehouseId,
			weight: weight,
			packageId: orderId
		}];
		var v = {};
		v.jsonStr = JSON.stringify(data);
		this.ajax({
			url: '/jkyun/oms/open/getpostage',
			type: 'post',
			data: jkUtils.jkGetSign(v)
		}).then((result) => {
			callback(result);
		});
	},

	// 清空控件的数据
	clearControl(control, callback) {
		if (control.getValue() || control.getText()) {
			switch (control.type) {
				case 'textbox':
				case 'uc-laydate':
					control.setValue('');
					break;
				case 'combobox':
				case 'buttonedit':
				case 'treeselect':
					control.setValue('');
					control.setText('');
					break;
				default:
					break;
			}
			// 处理清除后的事件回调
			if (callback)
				callback(control);
		}
	},

	// 批量注册清空控件事件
	regControlClearEvent(controlArr, callback) {
		var _this = this;
		$.each(controlArr, function(i, control) {
			control.on('closeclick', function() {
				_this.clearControl(control, callback);
			});
		});
	},

	// 预约/追加物流单号
	bookLogistic(params, grid, callback, ignoreDefault) {
		mini.mask();
		jkUtils.jkAjax({
			url: '/jkyun/wms/orderlist/postorder',
			data: params,
			type: 'post'
		}).done((res, status, code, result) => {
			if (callback && ignoreDefault)
				callback(result);
			else {
				var failData = null;
				if (result.result.data)
					failData = result.result.data.filter(function(item) {
						return !item.isSuccess;
					});

				if (failData && failData.length > 0) {
					var stopFailArr = failData.filter(function(item) {
						return item.aliExpressLogisticSolution == null || item
							.aliExpressLogisticSolution.length == 0;
					});
					if (stopFailArr.length > 0)
						showFailedMsg(stopFailArr);
					else {
						var planList = failData.filter(function(item) {
							return item.aliExpressLogisticSolution != null && item
								.aliExpressLogisticSolution.length > 0;
						});
						mini.open({
							// targetWindow: window,
							url: '/wms/order/logistic_project_selector.html',
							title: jkUtils.jki18n('base_msg_100022', '选择物流方案', 'base'),
							width: 860,
							height: 400,
							showModal: true,
							allowResize: false,
							onload: function() {
								var iframe = this.getIFrameEl();
								iframe.contentWindow.logisticProjectSelector.setData({
									planListJson: planList[0]
										.aliExpressLogisticSolution,
									orderId: planList[0].orderId
								});
							},
							ondestroy: function(action) {
								var orderIds = planList.map(function(item) {
									return item.orderId;
								});
								if (action === 'ok')
									reBookLogisticAndPrint(orderIds);
								else if (action == 'solution') {
									var iframe = this.getIFrameEl();
									var solution = mini.clone(iframe.contentWindow
										.logisticProjectSelector.getData());
									reBookLogisticAndPrint(orderIds, solution);
								}

								// 重新预约速卖通的物流单号
								function reBookLogisticAndPrint(orderIds, solution) {
									var bookParams = {
										orderIds: orderIds,
										type: 'order'
									};
									if (solution)
										$.extend(bookParams, solution);

									jkUtils.jkAjax({
										url: '/jkyun/wms/orderlist/postorder',
										data: bookParams,
										type: 'post'
									}).done((res, status, code, result) => {
										if (result.code == jkEnums.CommonRspCode
											.Success) {
											if (callback && ignoreDefault)
												callback(result);
											else {
												var failData = null;
												if (result.result.data)
													failData = result.result.data
													.filter(function(item) {
														return !item.isSuccess;
													});
												if (failData && failData.length > 0)
													showFailedMsg(failData);
												else
													jkUtils.showTip({
														content: result.msg
													});

												updateSuccessRows(grid, result.result
													.data);
											}
										}
									});
								}
							}
						});
					}
				} else
					jkUtils.showTip({
						content: result.msg
					});

				updateSuccessRows(grid, result.result.data);

				function updateSuccessRows(grid, resultData) {
					if (grid) {
						var successData = resultData.filter(function(item) {
							return item.isSuccess;
						});
						successData.forEach(function(item) {
							var row = grid.findRow(function(row) {
								return row.orderId == item.orderId;
							});
							var logisticNoArr = item.logisticNo.split(',');
							if (row)
								grid.updateRow(row, {
									logisticNo: logisticNoArr[0],
									otherLogisticNo: logisticNoArr.length > 1 ? logisticNoArr
										.slice(1).join(',') : ''
								});
						});
						if (callback && !ignoreDefault)
							callback(result);
					}
				}

				function showFailedMsg(failData) {
					var msg = failData.reduce(function(prev, cur) {
						return prev + (cur.postOrderNo ? cur.postOrderNo : '') + cur.message +
							'\r\n';
					}, '');

					jkUtils.layer({
						content: msg,
						state: 'error'
					});
				}
			}

		}).always(() => {
			mini.unmask(document.body);
		});
	},

	getEnumValues(enumCode, excludeIds) {
		var result = [];
		if (wmsEnums[enumCode])
			result = wmsEnums[enumCode].values;

		if (excludeIds && excludeIds.length > 0)
			result = result.filter(function(item) {
				return $.inArray(item.id, excludeIds) < 0;
			});

		return result;
	},
	showOrderDetail(options) {
		var defaultOptions = {
			// targetWindow: window,
			url: '/wms/order/invoice_detail.html',
			showHeader: true,
			title: jkUtils.jki18n('base_msg_100023', '发货单详情', 'base'),
			width: 1170,
			height: 600,
			allowResize: false,
			showModal: true,
			grid: null
		};
		options = $.extend({}, defaultOptions, options);
		if (!options.data) throw new Error('必须传入订单数据');
		options.onload = function() {
			var iframe = this.getIFrameEl();
			var data = options.data;
			iframe.contentWindow.invoicDetail.setData(data);
		};
		options.ondestroy = function(message) {
			var grid = options.grid;
			if (message == 'ok' && grid) {
				if (options.data.flag == 'byNo') {

				} else {
					var row = grid.findRow(function(row) {
						return row.orderId == options.data.selectRow.orderId;
					});

					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.getData();
					grid.updateRow(row, data);
				}
			}
		};
		mini.open(options);
	},
	showReceiptDetail(options) {
		var defaultOptions = {
			//targetWindow: window,
			url: '/common/order/receipt_detail.html',
			showHeader: true,
			title: jkUtils.jki18n('base_msg_100024', '收货单', 'base'),
			width: 1150,
			height: 600,
			allowResize: false,
			showModal: true,
			grid: null
		};
		options = $.extend({}, defaultOptions, options);
		if (!options.data) throw new Error('必须传入订单数据');
		options.onload = function() {
			var iframe = this.getIFrameEl();
			var data = options.data;
			iframe.contentWindow.receiptDetail.setData(data);
		};
		options.ondestroy = function(message) {
			var grid = options.grid;
			if (message == 'ok' && grid) {
				if (options.data.flag == 'byNo') {

				} else {
					var row = grid.findRow(function(row) {
						return row.orderId == options.data.selectRow.orderId;
					});

					var iframe = this.getIFrameEl();
					var data = iframe.contentWindow.getData();
					grid.updateRow(row, data);
				}
			}
		};
		mini.open(options);
	},

	// 统一比较条码是否存在
	doesSkuBarcodeExist(barcode, skuCodeSet) {
		var isCodeMatched = false;
		if (typeof skuCodeSet === 'string') {
			var allCode = skuCodeSet;
			if (allCode.indexOf(',') != 0)
				allCode = ',' + allCode + ',';
			isCodeMatched = allCode.indexOf(',' + barcode + ',') > -1;
		} else if (skuCodeSet instanceof Array)
			isCodeMatched = skuCodeSet.some(function(item) {
				return item == barcode;
			});

		return isCodeMatched;
	},

	getCursortPosition: function(element) {
		let cursorPos = 0;
		if (document.selection) { //IE
			var selectRange = document.selection.createRange();
			selectRange.moveStart('character', -element.value.length);
			cursorPos = selectRange.text.length;
		} else if (element.selectionStart || element.selectionStart == '0') {
			cursorPos = element.selectionStart;
		}
		return cursorPos;
	},

	setCaretPosition: function(element, pos) {
		if (element.setSelectionRange) {
			// IE Support
			element.focus();
			element.setSelectionRange(pos, pos);
		} else if (element.createTextRange) {
			// Firefox support
			var range = element.createTextRange();
			range.collapse(true);
			range.moveEnd('character', pos);
			range.moveStart('character', pos);
			range.select();
		}
	},
	//模块获取枚举名称
	getEnumName: function(enumType, id) {
		var enumValues = jkUtils.getEnumValues(enumType);
		var getenum = enumValues.find(enumValue => enumValue.id == id);
		return getenum ? getenum.name : '';
	},

	formatFloatValue: function(floatValue) {
		var result = Number(floatValue);
		return floatValue !== '' ? result : '';
	},

	// 用于渲染商品条码列，只显示第一个条码或辅助条码
	renderSkuBarcodeCell: function(data) {
		var result = '';
		if (data)
			result = data.split(',')[0];

		return result;
	},

	// 用于表单界面，生成右侧锚点导航
	createFormNav: function(className) {
		var $navAnchors = $('.' + className);
		var htmlStr = '<div class="form-nav-container"><ul class="form-nav">';
		$.each($navAnchors, function(index, anchor) {
			var $anchor = $(anchor);
			if (!$anchor.prop('id'))
				$anchor.attr('id', className + '-' + index);
			htmlStr += '<li class="form-nav-item"><a href="#' + $anchor.prop('id') + '">' + $anchor
				.text().split('(')[0].split('（')[0] + '</a></li>';
		});
		htmlStr += '</ul></div>';
		htmlStr += '<div class="form-nav-totop"></div>';
		$('body').append(htmlStr);
	},

	checkLogisticChanged: function(newData, oldData, callback) {
		var isLogisticChanged = false,
			isLocationChaged = false;
		isLogisticChanged = (oldData.logisticId != newData.logisticId || oldData.logisticNo != newData
			.logisticNo);
		isLocationChaged = (oldData.address != newData.address || oldData.mobile != newData.mobile ||
			oldData.phone != newData.phone || oldData.buyerName != newData.buyerName);

		if (isLocationChaged || isLogisticChanged) {
			mini.open({
				// targetWindow: window,
				url: isLocationChaged ? '/wms/order/location_change.html' :
					'/wms/order/logistic_change.html',
				title: jkUtils.jki18n('base_msg_100025', '修改确认', 'base'),
				width: 600,
				height: isLocationChaged ? 500 : 350,
				showModal: true,
				allowResize: false,
				onload: function() {
					var iframe = this.getIFrameEl();
					var data = {
						newData: newData,
						oldData: mini.clone(oldData)
					};
					iframe.contentWindow.setData(data);
				},
				ondestroy: function(action) {
					var resultData = null;
					if (action == 'ok') {
						var iframe = this.getIFrameEl();
						if (iframe.contentWindow.getData)
							resultData = iframe.contentWindow.getData();
					}
					callback(action, resultData);
				}
			});
		} else
			callback(null, null);
	},

	isMac: topWindow.isMac == 'mac' || navigator.platform.toUpperCase().indexOf('MAC') >= 0,

	// window和mac复制快捷键兼容
	copyKeydown: function(e, callback) {
		var isMac = jkUtils.isMac;
		if (isMac && e.metaKey) {
			if (e.keyCode === 67) {
				callback();
			}
		}
		if (!isMac && e.ctrlKey) {
			if (e.keyCode === 67) {
				callback();
			}
		}
	},

	// 深度判断两个对象是否完全相等
	// X: 对象1, y: 对象2, ignoreType:忽略强类型比较 例如 1== '1' 为true
	compareObj: function(x, y, ignoreType) {
		let p, i;

		if (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y))
			return true;

		if (x === y || (ignoreType && x == y))
			return true;

		if ((typeof x === 'function' && typeof y === 'function') ||
			(x instanceof RegExp && y instanceof RegExp) ||
			(x instanceof String && y instanceof String) ||
			(x instanceof Number && y instanceof Number)) {
			return x.toString() === y.toString();
		}

		if (x instanceof Date && y instanceof Date)
			return x.getTime() === y.getTime();

		if (!(x instanceof Object && y instanceof Object))
			return false;

		if (x.prototype !== y.prototype)
			return false;

		if (x.constructor !== y.constructor)
			return false;

		p = Object.keys(y);
		for (i = 0; i < p.length; i++) {
			if (!x.hasOwnProperty(p[i]))
				return false;
		}

		p = Object.keys(x);
		for (i = 0; i < p.length; i++) {
			if (!y.hasOwnProperty(p[i]))
				return false;

			if (!ignoreType && typeof y[p[i]] !== typeof x[p[i]])
				return false;

			if (!this.compareObj(x[p[i]], y[p[i]], ignoreType))
				return false;
		}

		return true;
	},

	// 指定需要的部分进行比较
	// x, y：要比较的两个对象
	// ignoreType：不进行强类型比较
	// specificKeys：指定要比较的键
	// excludeKeys：忽略要比较的键
	compareObjWith: function(options) {
		var objX = mini.clone(options.new),
			objY = mini.clone(options.old);
		if (options.excludeKeys && options.excludeKeys.length > 0) {
			options.excludeKeys.forEach(function(key) {
				delete objX[key];
				delete objY[key];
			});
		} else if (options.specificKeys && options.specificKeys.length > 0) {
			var newX = {},
				newY = {};
			options.specificKeys.forEach(function(key) {
				newX[key] = objX[key];
				newY[key] = objY[key];
			});
			objX = newX, objY = newY;
		}

		return jkUtils.compareObj(objX, objY, options.ignoreType);
	},

	// 批量操作提示
	batchTip: function(option) {
		var defaultOption = {
			grid: {}, //主表grid对象，取grid.getSelecteds() 行
			state: 'danger', //部分错误提示类型
			successTip: jkUtils.jki18n('com_operate_succeed', '操作成功',
				'common'), //全部成功的提示， result.data.length==0 表示全部成功
			isRemove: true, //是否删除行 成功的行 true：删除， false: 不删除
			isFireRowCheck: false, //是否触发rowcheck事件,在removeRows后触发，一般没用订单审核页面需要，右侧面板有区分
			result: {
				// data: [
				//     {
				//         businessId: '',
				//         businessType: null,
				//         code: null,
				//         explain: '',
				//         more: null
				//     }
				// ]
			},
			//过滤器，选中行和返回结果的关联 例：tradeId 关联 businessId  return row.tradeOrder.tradeId === data.businessId;
			filter: () => {
				return true;
			},
			//外键选择器  return outer.tradeOrder.tradeId;
			outerKeySelector: function() {
				return '';
			},
			//选择器 return inner.businessId;
			innerKeySelector: function(inner) {
				return inner.businessId;
			},
			//获取错误提示内容方法，不传默认提示 data.explain  return outer.tradeOrder.tradeNo + " " + inner.explain;
			resultSelector: (outer, inner) => {
				return inner.explain;
			},
			//提示后调用方法
			funcFinally: () => {

			},
			callback: null, //成功回调 做批量更新的操作，放弃触发详情事件
		};

		option = $.extend(true, defaultOption, option);

		let grid = option.grid,
			result = option.result,
			rows = grid.getSelecteds(),
			fireEvent = function(grid, rows, callback) {
				if (callback && typeof callback === 'function')
					callback(rows);

				//刷新选中行主表+其他信息
				grid.fire('selectionchanged', {
					selected: grid.getSelected(),
					record: grid.getSelected(),
					sender: grid,
					source: grid,
					type: 'select'
				});
			};
		if (!(result.data && result.data.length > 0)) {
			//全部成功
			jkUtils.showTip({
				state: 'success',
				content: option.successTip
			}, () => {
				if (option.funcFinally && typeof option.funcFinally == 'function')
					option.funcFinally();

				if (option.isRemove) {
					grid.removeRows(rows, true);
					if (option.isFireRowCheck)
						grid.fire('rowcheck');
				} else {
					fireEvent(grid, rows, option.callback);
				}
			});
			return;
		}

		//成功的行, 部分成功
		var successRows = _.differenceWith(rows, result.data, option.filter);
		if (option.isRemove) {
			if (successRows.length > 0)
				grid.removeRows(successRows);
			if (option.isFireRowCheck)
				grid.fire('rowcheck');
		} else {
			fireEvent(grid, successRows, option.callback);
		}

		//部分成功的错误消息
		var msgArr = Enumerable.from(rows).join(Enumerable.from(result.data), option.outerKeySelector, option
			.innerKeySelector, option.resultSelector).toArray();
		if (msgArr.length > 0)
			jkUtils.layer({
				content: msgArr.join('<br>'),
				state: option.state == "danger" ? "error" : option.state,
				callback: function() {
					if (option.funcFinally && typeof option.funcFinally == 'function')
						option.funcFinally();
				}
			});
	},

	// 客户端数据缓存
	clientCache: {
		cacheDict: {}, // 数据缓存容器

		// 用于给类似erpData这样已经定好方法和参数的调用
		// dataKey: 唯一键
		// func: 取值方法
		// params: 取值方法的参数
		// isForceReget: 是否强制刷新
		getByKeyAndFunc: function(dataKey, func, params, isForceReget) {
			var callback = null;
			var index = params.length - 1;
			while (index > -1) {
				if (typeof params[index] === 'function') {
					callback = params[index];
					break;
				}
				index--;
			}
			if (this.cacheDict[dataKey] && !isForceReget) {
				if (callback)
					return callback(mini.clone(this.cacheDict[dataKey]));
				else
					return this.cacheDict[dataKey];
			}

			if (func)
				func(...params);
			else {
				if (callback)
					callback(null);
			}
		},

		// dataKey: 唯一键
		// ajaxConfig: 包含{url: xxxx, type: xxxx, async: xxx}等
		// callback: 回调函数
		// isForceReget: 是否强制刷新
		getByKey: function(dataKey, ajaxConfig, callback, isForceAjaxGet) {
			var that = this;
			if (this.cacheDict[dataKey] && !isForceAjaxGet) {
				if (callback)
					return callback(this.cacheDict[dataKey]);
				else
					return this.cacheDict[dataKey];
			}

			if (ajaxConfig) {
				if (!ajaxConfig.data || ((typeof ajaxConfig.data === "object") && !ajaxConfig.data.sign)) {
					jkUtils.jkAjax(ajaxConfig).then(result => {
						that.cacheDict[dataKey] = $.extend([], result.data);
						if (callback)
							callback(that.cacheDict[dataKey]);
					});
				} else {
					jkUtils.jkAjax(ajaxConfig).then(result => {
						that.cacheDict[dataKey] = $.extend([], result.data);
						if (callback)
							callback(that.cacheDict[dataKey]);
					});
				}
			} else {
				if (callback)
					callback(null);
			}
		},

		// 设置键对应的值
		setByKey: function(dataKey, data, isForceReset) {
			if (isForceReset || !this.cacheDict[dataKey])
				this.cacheDict[dataKey] = mini.clone(data);
		},

		// 移除键对应的值
		removeByKey: function(dataKey) {
			delete this.cacheDict[dataKey];
		},

		// 获取键对应的值
		getDataByKey: function(dataKey) {
			return this.cacheDict[dataKey];
		},
	},

	// 判断两个变量是否相同
	isEqual: function(o, x) {
		if (!o || !x) {
			return false;
		}
		var p;
		for (p in o) {
			if (typeof(x[p]) == 'undefined') {
				return false;
			}
		}
		for (p in o) {
			if (o[p]) {
				switch (typeof(o[p])) {
					case 'object':
						if (!this.isEqual(o[p], x[p])) {
							return false;
						}
						break;
					case 'function':
						if (typeof(x[p]) == 'undefined' ||
							(p != 'equals' && o[p].toString() != x[p].toString()))
							return false;
						break;
					default:
						if (o[p] != x[p]) {
							return false;
						}
				}
			} else {
				if (x[p])
					return false;
			}
		}
		for (p in x) {
			if (typeof(o[p]) == 'undefined') {
				return false;
			}
		}
		return true;
	},

	// 删除url中指定的参数
	funcUrlDel: function(loca, name) {
		let baseUrl = '';
		if (loca.indexOf('&') > -1) {
			baseUrl = loca.split('?')[0] + '?';
		} else {
			baseUrl = loca.split('?')[0];
		}
		let query = loca.split('?')[1];
		if (query && query.indexOf(name) > -1) {
			var obj = {};
			var arr = query.split("&");
			for (var i = 0; i < arr.length; i++) {
				arr[i] = arr[i].split("=");
				obj[arr[i][0]] = arr[i][1];
			}
			delete obj[name];
			var url = baseUrl + JSON.stringify(obj).replace(/[\"\{\}]/g, "").replace(/\:/g, "=").replace(/\,/g,
				"&");
			return url;
		} else {
			return loca;
		}
	},

	//事件的分发处理函数
	//整个过程是支持异步的
	createAndDispatchEvent: function(eventName, tab, params) {
		var customPormise = new Promise(function(resolve) {
			var next = function() {
				resolve();
			};
			var event = {
				detail: {
					activeTab: tab,
					cancel: false,
					next: next,
					params
				}
			};
			var windowEvent = new CustomEvent(eventName, event);
			if (tab._iframeEl) {
				tab._iframeEl.contentWindow.dispatchEvent(windowEvent);
			}
			if (event.detail.cancel === false) {
				next();
			}
		})
		return customPormise;
	},

	loadJs: function(url, callback) {
		const script = document.createElement('script')
		script.type = 'text/javascript';

		const fn = callback || new Function();
		if (script.readyState) {
			script.onreadystatechange = function() {
				if (script.readyState == 'loaded' || script.readyState == 'complete') {
					script.onreadystatechange = null;
					fn();
				}
			}
		} else {
			script.onload = function() {
				fn();
			}
		}

		script.src = url;
		document.getElementsByTagName('head')[0].appendChild(script);
	},

	//收集客户端按钮点击事件的数据
	// options = {
	//     eventCode : e.eventCode, // 事件编号
	//     eventName: e.eventName, // 事件名称（xxx按钮点击事件）
	//     eventResult: e.eventResult,   //事件结果
	//     moduleCode: e.moduleCode,// 菜单
	//     moduleName: e.moduleName,
	// }
	setClickEvent: function(options) {
		if (!options || typeof(options.eventResult) != 'number' || !options.eventCode || !options.eventName || !
			options.moduleCode || !options.moduleName) return console.error('请传入正确的参数格式');
		topWindow.buttonEventList.setter(options)
	},

	localStorage: {
		encryptKey: '', // 加密解密需要的key
		encryptIv: '', // 加密解密需要的偏移量
		flushDirty: false, // 是否需要清理数据
		get: function(key, namespace) {
			let me = jkUtils.localStorage
			if (!key) {
				console.warn(`key is not found!`);
				return
			}
			key = me._normalizeKey(key)
			let keyName = namespace ? `${namespace}_${key}` : key
			const storageValue = localStorage.getItem(keyName)
			if (namespace && !storageValue) {
				// 兼容老数据
				let value = localStorage.getItem(key)
				value && jkUtils.localStorage.set({
					namespace,
					key,
					value
				})
				jkUtils.localStorage.remove(key)
				return value
			}
			let storageJson = {};
			try {
				storageJson = JSON.parse(storageValue)
			} catch (error) {
				return storageValue
			}
			if (!storageJson) return storageValue
			const {
				value,
				createTime,
				expire,
				encrypt
			} = storageJson
			if (!createTime) return storageValue; // 没有createTime表示是老数据，直接返回storageValue
			if (expire && Date.now() - createTime > expire) {
				localStorage.removeItem(keyName)
				return null
			}
			let result = encrypt ? me._decrypt(value) : value
			try {
				return JSON.parse(result)
			} catch (e) {
				return result
			}
		},
		/**
		 * @description localStorage 存储
		 * @param options {Object} namespace：命名空间；key：键值；value：存储的值；expire：过期时间；encrypt：是否需要加密
		 */
		set: function({
			namespace = '',
			key,
			value = '',
			expire = '',
			encrypt
		}) {
			let me = jkUtils.localStorage
			if (!me._isLocalStorageUsable) return
			if (!key) {
				console.warn(`key is not found!`);
				return
			}
			key = me._normalizeKey(key)
			let keyName = namespace ? `${namespace}_${key}` : key
			let data = {
				value: JSON.stringify(value),
				createTime: Date.now()
			}
			expire && (data.expire = expire)
			encrypt && (data.encrypt = encrypt)
			if (encrypt) {
				jkUtils.loadJs('/component/thirdparty/crypto-js.min.js', () => {
					data.value = me._encrypt(data.value)
					data = JSON.stringify(data)
					set(keyName, data)
				})
			} else {
				data = JSON.stringify(data)
				set(keyName, data)
			}

			function set(key, value) {
				try {
					// window.localStorage.setItem(key, value)
					me.flushDirty = true
				} catch (e) {
					if (me.flushDirty) { // 尝试清理过期脏数据
						me._flushDirtyRead()
						set(key, value)
						me.flushDirty = false
						return
					}
					if (
						e.name === 'QuotaExceededError' ||
						e.name === 'NS_ERROR_DOM_QUOTA_REACHED'
					) {
						console.warn('localStorage 存储已达上限')
					}
					console.warn('localStorage 存储失败')
				}
			}
		},
		remove: function(key, namespace) {
			let me = jkUtils.localStorage
			if (!key) {
				console.warn(`key is not found!`);
				return
			}
			key = me._normalizeKey(key)
			let keyName = namespace ? `${namespace}_${key}` : key
			localStorage.removeItem(keyName)
		},
		// 清理指定命名空间下的数据
		clear: function(namespace) {
			if (!namespace) return
			const keys = Object.keys(localStorage);
			keys.forEach(key => {
				if (key.indexOf(`${namespace}_`) === 0) {
					localStorage.removeItem(key);
				}
			})
		},
		// 检验浏览器是否支持localStorage
		_isLocalStorageUsable: (function() {
			let isSupport = false
			const testKey = '__localStorage_support_test'
			const testValue = 'test'
			try {
				localStorage.setItem(testKey, testValue)
				isSupport = localStorage.getItem(testKey) === testValue
				localStorage.removeItem(testKey)
			} catch (e) {
				if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
					console.warn('localStorage 存储已达上限')
				} else {
					console.warn('当前浏览器不支持localStorage')
				}
			}
			return isSupport
		})(),
		// 清理过期脏数据
		_flushDirtyRead: function() {
			Object.entries(localStorage).forEach(([key, value]) => {
				var item = null
				try {
					item = JSON.parse(value)
				} catch (e) {}
				if (item &&
					item.expire &&
					Date.now() - item.createTime > item.expire
				) {
					localStorage.removeItem(key)
				}
			})
		},
		// 序列化key值
		_normalizeKey: function(key) {
			if (typeof key !== 'string') {
				console.warn(`${key} used as a key, but it is not a string.`);
				key = String(key);
			}
			return key;
		},
		// 加密函数
		_encrypt: function(word) {
			if (!CryptoJS) {
				console.warning('CryptoJS is not defined!')
				return ''
			}
			const me = jkUtils.localStorage
			me.getEncryptKey();
			let srcs = CryptoJS.enc.Utf8.parse(word);
			let encrypted = CryptoJS.AES.encrypt(srcs, me.encryptKey, {
				iv: me.encryptIv,
				mode: CryptoJS.mode.CBC,
				padding: CryptoJS.pad.Pkcs7
			});
			return encrypted.ciphertext.toString().toUpperCase();
		},
		// 解密函數
		_decrypt: function(word) {
			if (!CryptoJS) {
				console.warning('CryptoJS is not defined!')
				return ''
			}
			const me = jkUtils.localStorage
			me.getEncryptKey();
			let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
			let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
			let decrypt = CryptoJS.AES.decrypt(srcs, me.encryptKey, {
				iv: me.encryptIv,
				mode: CryptoJS.mode.CBC,
				padding: CryptoJS.pad.Pkcs7
			});
			let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
			return decryptedStr.toString();
		},
		getEncryptKey: function() {
			const me = jkUtils.localStorage
			me.encryptKey = me.encryptKey || CryptoJS.enc.Utf8.parse("1234123412ABCDEF");
			me.encryptIv = me.encryptIv || CryptoJS.enc.Utf8.parse("ABCDEF1234123412");
		}
	},

	/**
	 * @author: Zhouqi
	 * @description: 打开页面是否自动开启新功能引导
	 * @param {boolean} isAuto
	 * @return {function} 新功能引导执行函数
	 */
	autoGuid(isAuto = false) {
		return function() {
			function closeAllGuide() {
				let nowStampStr = String((new Date()).valueOf());
				// if (typeof window.jk_setLocalSet === "function") {
				// 	try {
				// 		window.jk_setLocalSet("closeGuideVer", String((new Date()).valueOf()));
				// 	} catch (e) {
				// 		console.log(e);
				// 	}
				// } else {
				// 	localStorage.setItem("closeGuideVer", nowStampStr);
				// }
			}

			function setModuleGuideVer(moduleId, gmtCreate) {
				let itemKey = `${moduleId}_guide`;
				// if (typeof window.jk_setLocalSet === "function") {
				// 	try {
				// 		window.jk_setLocalSet(itemKey, String(gmtCreate));
				// 	} catch (e) {
				// 		console.log(e);
				// 	}
				// } else {
				// 	localStorage.setItem(itemKey, gmtCreate);
				// }
			}

			function isAfterModuleVer(moduleId, gmtCreate) {
				let itemKey = `${moduleId}_guide`;
				// if (typeof window.jk_getLocalSet === "function") {
				// 	try {
				// 		let resultStr = window.jk_getLocalSet(),
				// 			result = JSON.parse(resultStr);
				// 		for (let key in result) {
				// 			let item = result[key];
				// 			if (item[itemKey]) {
				// 				if (gmtCreate > item[itemKey]) return true;
				// 				else return false;
				// 			}
				// 		}
				// 		return true;
				// 	} catch (e) {
				// 		return false;
				// 	}
				// } else {
				// 	let moduleGuideGmt = localStorage.getItem(itemKey);
				// 	if (moduleGuideGmt) {
				// 		if (gmtCreate > moduleGuideGmt) return true;
				// 		else return false;
				// 	} else {
				// 		return true;
				// 	}
				// }
			}

			//判断传入导航时间戳是否在关闭所有导航的版本时间戳之后
			//如果在之后, 则返回true, 否则返回false
			function isAfterCloseVer(gmtCreate) {
				let closeVer;
				// if (typeof window.jk_getLocalSet === "function") {
				// 	try {
				// 		closeVer = window.jk_getLocalSet();
				// 		let result = JSON.parse(closeVer);
				// 		for (let key in result) {
				// 			let item = result[key];
				// 			if (item.closeGuideVer) {
				// 				closeVer = item.closeGuideVer;
				// 				if (gmtCreate > closeVer) {
				// 					return true;
				// 				} else {
				// 					return false;
				// 				}
				// 			}
				// 		}
				// 		return true;
				// 	} catch (e) {
				// 		return false;
				// 	}
				// } else {
				// 	let closeAllGuideGmt = localStorage.getItem("closeGuideVer");
				// 	if (closeAllGuideGmt) {
				// 		if (gmtCreate > closeAllGuideGmt) return true;
				// 		else return false;
				// 	} else {
				// 		return true;
				// 	}
				// }
			}
			//处理页面功能导航相关逻辑
			var moduleInfo = jkUtils.jkGetModuleInfo();
			// const isUserFire = !isAuto;
			// isAuto === false && (moduleInfo.isAuto = false);
			var moduleId;
			try {
				moduleId = moduleInfo.moduleId;
			} catch (e) {
				moduleId = ''
			}
			var pageGuide;
			var manualpageGuide;
			let isPreview = false;
			if (topWindow.guideConfig) {
				pageGuide = topWindow.guideConfig.find(function(item) {
					return item.moduleId === moduleId &&
						isAfterCloseVer(item.gmtCreate) &&
						isAfterModuleVer(item.moduleId, item.gmtCreate)
				});
				manualpageGuide = topWindow.guideConfig.find(function(item) {
					return item.moduleId === moduleId && item.guideOccasion === 3
				});
			}

			if (topWindow.__tempGuideData__ && topWindow.__tempGuideData__.moduleId === moduleId) {
				pageGuide = topWindow.__tempGuideData__;
				manualpageGuide = topWindow.__tempGuideData__;
				isPreview = true;
			}
			if (manualpageGuide) {
				let guideState = 0;
				var script = document.createElement('script');
				script.type = "text/javascript";
				script.src = "/common/guide/intro.js";
				script.onload = function() {
					guideState++;
					if (guideState === 2) {
						startGuide();
					}
				}
				document.querySelector('head').appendChild(script);

				var link = document.createElement('link');
				link.rel = "stylesheet";
				link.type = "text/css";
				link.href = "/common/guide/intro.css";
				document.querySelector('head').appendChild(link);
				link.onload = function() {
					guideState++;
					if (guideState === 2) {
						startGuide();
					}
				};

				function startGuide() {
					var guideElementList;
					try {
						guideElementList = JSON.parse(manualpageGuide.guideElementJson);
					} catch (e) {
						console.log(e);
						guideElementList = [];
					}
					var introConfig = guideElementList.map(function(item) {
						return {
							element: document.querySelector(`#${item.introElement}`),
							intro: item.introContent
						}
					});
					// guideOccasion === 3 引导时机为手动引导
					if (manualpageGuide.guideOccasion === 3) {
						const manualIntro = introJs().setOptions({
							steps: introConfig,
							exitOnOverlayClick: false,
							nextLabel: "下一个",
							prevLabel: "上一个",
							doneLabel: "关闭",
							checkboxLabel: "关闭所有系统指引",
							keyboardNavigation: false,
							helperElementPadding: 0,
						});
						manualIntro.oncheckchange(function(e) {
							if (e === true) {
								closeAllGuide();
							}
						});
						manualIntro.onbeforeexit(function() {
							setModuleGuideVer(moduleId, (new Date()).valueOf());
						});
						// window.manualGuide = manualIntro;
					}
				}
			}
			if (pageGuide) {
				//动态插入的script标签和link标签都加载完成后才可以展示引导
				//这里设置标志位guideState, 每次完成一个关键资源加载即加一
				//当标志值为2时, 开始展示前端引导
				let guideState = 0;
				//如果查找到模块有对应的功能导航, 则插入导航所需的JS和CSS
				var script = document.createElement('script');
				script.type = "text/javascript";
				script.src = "/common/guide/intro.js";
				script.onload = function() {
					guideState++;
					if (guideState === 2) {
						startGuide();
					}
				}
				document.querySelector('head').appendChild(script);

				var link = document.createElement('link');
				link.rel = "stylesheet";
				link.type = "text/css";
				link.href = "/common/guide/intro.css";
				document.querySelector('head').appendChild(link);
				link.onload = function() {
					guideState++;
					if (guideState === 2) {
						startGuide();
					}
				};

				function startGuide() {
					var guideElementList;
					try {
						guideElementList = JSON.parse(pageGuide.guideElementJson);
					} catch (e) {
						console.log(e);
						guideElementList = [];
					}
					var introConfig = guideElementList.map(function(item) {
						return {
							element: document.querySelector(`#${item.introElement}`),
							intro: item.introContent
						}
					});

					if (pageGuide.guideOccasion === 3) {
						const manualIntro = introJs().setOptions({
							steps: introConfig,
							exitOnOverlayClick: false,
							nextLabel: "下一个",
							prevLabel: "上一个",
							doneLabel: "关闭",
							checkboxLabel: "关闭所有系统指引",
							keyboardNavigation: false,
							helperElementPadding: 0,
						});
						manualIntro.oncheckchange(function(e) {
							if (e === true) {
								closeAllGuide();
							}
						});
						manualIntro.onbeforeexit(function() {
							setModuleGuideVer(moduleId, (new Date()).valueOf());
						});
						// window.firstOpenManualGuide = manualIntro;
					} else {
						introJs().setOptions({
							steps: introConfig,
							exitOnOverlayClick: false,
							nextLabel: "下一个",
							prevLabel: "上一个",
							doneLabel: "关闭",
							checkboxLabel: "关闭所有系统指引",
							keyboardNavigation: false,
							helperElementPadding: 0,
						}).oncheckchange(function(e) {
							if (e === true) {
								closeAllGuide();
							}
						}).onbeforeexit(function() {
							setModuleGuideVer(moduleId, (new Date()).valueOf())
						}).start();
					}
				}
			}
		}
	},

	/**
	 * @description: 检测图片地址并设置src属性值
	 * @param {Object} imgEl 图片DOM元素
	 * @param {String} realSrc 图片地址
	 * @param {String} defaultSrc 默认图片地址
	 */
	imgSrcTestAndSet(imgEl, realSrc = '', defaultSrc = '') {
		if (!imgEl) return '';
		let imgSrc = '';
		if (realSrc && !['[]', 'null', 'undefined'].includes(realSrc)) {
			imageIsExist(realSrc).then((result) => {
				imgSrc = result ? realSrc : (defaultSrc || "");
				imgEl.attr('src', imgSrc)
			}).catch(() => {
				imgSrc = defaultSrc || ""
				imgEl.attr('src', imgSrc)
			});
		} else {
			imgSrc = defaultSrc || ""
			imgEl.attr('src', imgSrc)
		}

		// 检测图片是否存在
		function imageIsExist(url) {
			return new Promise((resolve) => {
				let img = new Image();
				img.onload = function() {
					if (this.complete == true) {
						resolve(true);
						img = null;
					}
				}
				img.onerror = function() {
					resolve(false);
					img = null;
				}
				img.src = url;
			})
		}
	},

	// 自定义导出模板
	// 模板相关数据存取ccs actionName: info.moduleId || info.moduleCode 或自定义code
	cunstomExport: {
		/*
		获取当前模块自定义导出模板
		callback：获取模板成功的回调函数，回调形参为当前页面所有的模板数据
		temGroupCode:'', 模板分组code 不同页面可根据此值获取公共模板(模板数据默认根据页面modeleCode存取，获取模板时传入temGroupCode值,则根据temGroupCode获取)
		 */
		getCunstomExport: function(callback, temGroupCode) {
			let moduleCode = '';
			if (temGroupCode) {
				moduleCode = temGroupCode
			} else {
				const info = jkUtils.jkGetModuleInfo();
				moduleCode = (info) ? info.moduleId || info.moduleCode : '';
			}
			if (!moduleCode) {
				console.warning('未获取到当前页面的moduleCode');
				return;
			}
			let actionName = moduleCode + '_custom_export_order';
			jkUtils.jkGetCCSConfig(moduleCode, actionName).then(res => {
				if (callback && typeof callback === 'function') {
					callback(res)
				}
			})
		},

		/*
		为当前模块 新增、编辑自定义导出模板，打开新增/编辑弹框
		optins={
		        title: '新增自定义导出模板', //标题
		        action: 'add', // add 新增、edit 编辑
		        defaultSelectFlag:false,// boolean 是否默认选择表格字段为导出字段，false默认已选字段为空
		        currMenuItems: [],//当前页面所有的模板数据
		        sender:null,//当前编辑的模板数据，action为'edit'时必填
		        successCallback：//新增、编辑 成功回调函数，回调形参为保存后当前页面所有的模板数据

		        temGroupCode:'',// 模板分组code 不同页面可根据此值存取公共模板 (模板数据默认根据页面modeleCode存取，新增模板时传入temGroupCode值,则根据temGroupCode存储)

		        mainGrid: null, //必填  需要导出的主表的mini对象
		        mainGridMaxNum:80,//主表数量限制
		        mainGridTabName:'订单信息',//待选列中主表字段tab名
		        mainGridType：'订单字段',//已选列中主表字段类型名
		        mainGridExcludeFields:[],//主表不支持导出的字段

		        childGrid:null,//需要导出的子表
		        childGridMaxNum:80,//子表数量限制
		        childGridTabName:'货品信息',//子表待选列tab名
		        childGridType:'货品字段',//子表字段类型
		        childGridExcludeFields:[],//子表不支持导出的字段

		        showPlainCheckFlag:false,//boolean是否显示明文勾选框
		        selectlabel:'收货人信息明文导出',//明文勾选框label
		        selectTipContent:'',//气泡提示框内容
		}
		*/
		openCunstomExportTemSet: function(option = {}) {
			option = $.extend({
				title: jkUtils.jki18n('base_custom_export_field1', '新增自定义导出模板', 'base'),
				action: 'add',
				defaultSelectFlag: false,
				currMenuItems: [],
				sender: null,

				temGroupCode: '',

				mainGrid: null,
				mainGridMaxNum: 80,
				mainGridTabName: jkUtils.jki18n('base_custom_export_field3', '订单信息', 'base'),
				mainGridType: jkUtils.jki18n('base_custom_export_field4', '订单字段', 'base'),
				mainGridExcludeFields: [],

				childGrid: null,
				childGridMaxNum: 80,
				childGridTabName: jkUtils.jki18n('base_custom_export_field5', '货品信息', 'base'),
				childGridType: jkUtils.jki18n('base_custom_export_field6', '货品字段', 'base'),
				childGridExcludeFields: [],

				packageFields: [],
				packageTabName: '包裹信息',
				packageFieldType: '包裹级字段',
				childpackageMaxNum: 80,

				showPlainCheckFlag: false,
				selectlabel: jkUtils.jki18n('base_custom_export_field15', '收货人信息明文导出', 'base'),
				selectTipContent: `${jkUtils.jki18n('base_custom_export_field7', '1.当该配置项勾选之后，若自定义导出列中配置了收货人、手机号、收货地址字段，将按照明文的格式进行导出；', 'base')}<br>
                                ${jkUtils.jki18n('base_custom_export_field8', '2.需平台消费者敏感信息数据未加密，才可以支持明文导出，否则将按照密文的格式导出数据。', 'base')}`

			}, option);

			if (!option.mainGrid || (option.action === 'edit' && !option.sender)) return;
			mini.open({
				// targetWindow: window,
				url: '/common/custom_export/custom_export_template.html',
				title: option.title,
				width: '860',
				height: '600',
				showHeader: true,
				showModel: true,
				allowResize: false,
				showCloseButton: true,
				onload: function() {
					let iframe = this.getIFrameEl();
					let data = {};
					data.action = option.action;
					data.currMenuItems = option.currMenuItems;
					data.sender = option.sender || '';
					data.list = [];
					data.showPlainCheckFlag = option.showPlainCheckFlag;
					data.selectlabel = option.selectlabel;
					data.selectTipContent = option.selectTipContent;

					function cloneColumn(col) {
						return {
							field: col.field,
							name: col.name,
							firstNoDisplay: col.firstNoDisplay,
							header: col.exportFieldName || col.header,
							isRequiredEditor: (typeof col.editor !== 'undefined') && col.editor
								.required,
						};
					}
					// 处理主表列信息
					let mainColsInfo = {
						tab: 'mainGrid',
						tabName: option.mainGridTabName,
						businessColumns: [],
						userDiyColumns: []
					}
					data.mainGridMaxNum = option.mainGridMaxNum;
					data.mainGridType = option.mainGridType;
					option.mainGrid.devColumns.forEach(function(col) {
						if (!option.mainGridExcludeFields.includes(col.exportField || col
								.field)) {
							mainColsInfo.businessColumns.push(cloneColumn(col));
						}
					});
					if (!(option.action === 'add' && !option.defaultSelectFlag)) {
						if (option.sender && option.sender.exportCols && _.isArray(option.sender
								.exportCols)) {
							option.sender.exportCols.forEach(function(col) {
								if (col.parentId === 'mainGrid') {
									let currCol = option.mainGrid.devColumns.find(_ => _
										.field === col.field);
									if (currCol) {
										if (!option.mainGridExcludeFields.includes(currCol
												.exportField || currCol.field)) {
											mainColsInfo.userDiyColumns.push(cloneColumn(
												currCol));
										}
									}
								}

							});
						} else {
							option.mainGrid.columns.forEach(function(col) {
								if (!option.mainGridExcludeFields.includes(col
										.exportField || col.field)) {
									mainColsInfo.userDiyColumns.push(cloneColumn(col));
								}
							});
						}
					}

					data.list.push(mainColsInfo);
					// 处理子表列信息
					if (option.childGrid) {
						data.childGridMaxNum = option.childGridMaxNum;
						data.childGridType = option.childGridType;
						let childColsInfo = {
							tab: 'goodsGrid',
							tabName: option.childGridTabName,
							businessColumns: [],
							userDiyColumns: []
						}
						option.childGrid.devColumns.forEach(function(col) {
							if (!option.childGridExcludeFields.includes(col.exportField ||
									col.field)) {
								childColsInfo.businessColumns.push(cloneColumn(col));
							}
						});
						if (!(option.action === 'add' && !option.defaultSelectFlag)) {
							if (option.sender && option.sender.exportCols && _.isArray(option.sender
									.exportCols)) {
								option.sender.exportCols.forEach(function(col) {
									if (col.parentId === 'goodsGrid') {
										let currCol = option.childGrid.devColumns.find(_ =>
											_.field === col.field);
										if (currCol) {
											if (!option.childGridExcludeFields.includes(
													currCol.exportField || currCol.field)) {
												childColsInfo.userDiyColumns.push(
													cloneColumn(currCol));
											}
										}
									}

								});
							} else {
								option.childGrid.columns.forEach(function(col) {
									if (!option.childGridExcludeFields.includes(col
											.exportField || col.field)) {
										childColsInfo.userDiyColumns.push(cloneColumn(col));
									}
								});
							}
						}
						data.list.push(childColsInfo);
					}

					// 处理‘包裹信息’
					if (jkUtils.jkIsArray(option.packageFields) && option.packageFields.length) {
						data.childpackageMaxNum = option.childpackageMaxNum;
						data.packageTabName = option.packageTabName;
						data.packageFieldType = option.packageFieldType;
						data.packageFlag = true;
						let packageColsInfo = {
							tab: 'packageInfo',
							tabName: option.packageTabName,
							businessColumns: option.packageFields,
							userDiyColumns: [],
						}

						// 处理已选字段
						if (option.action === 'edit' && option.sender && option.sender.exportCols &&
							_.isArray(option.sender.exportCols)) {
							option.sender.exportCols.forEach(function(col) {
								if (col.parentId === 'packageInfo') {
									let currCol = option.packageFields.find(_ => _.field ===
										col.field);
									currCol && packageColsInfo.userDiyColumns.push(
										cloneColumn(currCol));
								}
							});
						}
						data.list.push(packageColsInfo);
					}
					iframe.contentWindow.selectColumnsPage.setData(data);
				},
				ondestroy: function(message) {
					if (message === "ok") {
						let iframe = this.getIFrameEl();
						let currMenuItems = option.currMenuItems || [];
						//获取需要保存的列相关数据
						let {
							exportCols,
							customExportName,
							plainText
						} = iframe.contentWindow.selectColumnsPage.getData();
						if (option.action === 'add') {
							let theTem = {
								text: customExportName, //模板名称
								exportCols: exportCols, //当前模板配置的导出列信息
								default: currMenuItems.length === 0 ? true : false, //是否为默认模板,
								plainText: plainText || false //收货人信息是否明文导出
							}
							option.temGroupCode && (theTem.temGroupCode = option.temGroupCode)
							currMenuItems.push(theTem);
							option.layerText = jkUtils.jki18n('base_custom_export_field9',
								'新增自定义导出模板成功', 'base');
							jkUtils.cunstomExport.storeMenuItems(currMenuItems, option);
						} else {
							let currMenuItem = currMenuItems.find(_ => _.text === option.sender
								.text);
							currMenuItem.text = customExportName;
							currMenuItem.exportCols = exportCols;
							currMenuItem.plainText = plainText;
							option.layerText = jkUtils.jki18n('base_custom_export_field10',
								'修改自定义导出模板成功', 'base');
							option.temGroupCode = currMenuItem.temGroupCode || '';
							jkUtils.cunstomExport.storeMenuItems(currMenuItems, option);
						}
					}
				}
			})
		},

		/*
		删除自定义导出模板
		currMenuItem：null,//当前要删除的模板数据   必填
		allTemData：当前页面所有的模板数据   必填
		successCallback：删除模板成功的回调函数，回调形参为删除后的所有模板数据
		 */
		deleteCunstomExportTem: function(currMenuItem, allTemData = [], successCallback) {
			if (!currMenuItem || !allTemData || !allTemData.length) return;
			let confirmTip = jkUtils.jki18n('base_custom_export_field11', '您确定要删除自定义导出模板{0}吗？', 'base');
			confirmTip = String.format(confirmTip, currMenuItem.text)

			mini.confirm(confirmTip, jkUtils.jki18n('base_tip', '提示', 'base'), function(action) {
				if (action === 'ok') {
					allTemData = allTemData.filter(_ => _.text !== currMenuItem.text);
					let option = {
						temGroupCode: currMenuItem.temGroupCode || ''
					};
					(successCallback && typeof successCallback === 'function') ? option
						.successCallback = successCallback: '';
					option.layerText = jkUtils.jki18n('base_custom_export_field12', '删除模板成功', 'base');
					jkUtils.cunstomExport.storeMenuItems(allTemData, option)
				}
			});
		},

		/*
		 设置、取消 默认模板
		currMenuItem：null,//当前要设置的模板数据   必填
		allTemData：当前页面所有的模板数据   必填
		successCallback：设置成功的回调函数，回调形参为设置后的所有模板数据
		 */
		setDefaultTem(currMenuItem, allTemData = [], successCallback) {
			if (!currMenuItem || !allTemData || !allTemData.length) return;
			let defaultValue = currMenuItem.default;
			allTemData.forEach(item => {
				if (item.text === currMenuItem.text) {
					item.default = !defaultValue;
				} else {
					item.default = false;
				}
			})
			let option = {
				temGroupCode: currMenuItem.temGroupCode || ''
			};
			(successCallback && typeof successCallback === 'function') ? option.successCallback = successCallback:
				'';
			option.layerText = defaultValue ? jkUtils.jki18n('base_custom_export_field13', '取消默认模板成功', 'base') :
				jkUtils.jki18n('base_custom_export_field14', '设置默认模板成功', 'base');
			jkUtils.cunstomExport.storeMenuItems(allTemData, option)
		},

		// 保存模板数据
		storeMenuItems: function(currMenuItems, option) {
			let moduleCode = '';
			const {
				temGroupCode,
				layerText,
				successCallback
			} = option || {};
			if (temGroupCode) {
				moduleCode = temGroupCode
			} else {
				const info = jkUtils.jkGetModuleInfo();
				moduleCode = (info) ? info.moduleId || info.moduleCode : '';
			}
			if (!moduleCode) return;
			let actionName = moduleCode + '_custom_export_order';
			jkUtils.jkSaveCCSConfig({
				moduleCode: moduleCode,
				actionName: actionName,
				configs: currMenuItems,
				callback: function() {
					jkUtils.layer({
						content: layerText
					});
					if (successCallback && typeof successCallback === 'function') successCallback(
						currMenuItems);
				}
			})

		}
	},

	throttle(fn, delay = 0, immediately = false) {
		let preTime = Date.now();
		return function() {
			const context = this;
			const args = arguments;
			const nowTime = Date.now();
			if (immediately) {
				fn.apply(context, args);
				immediately = false;
			} else if (nowTime - preTime > delay) {
				fn.apply(context, args);
				preTime = Date.now();
			}
		}
	},

	/*
	     创建提示框(公共方法仅添加提示框，图标需自主添加,图标类名需包含" tooltip-icon")
	     byDataTitle：false,//是否根据图标的自定义属性data-title创建提示.为true时可自定义图标关键类名keyClass
	     config:[//提示框配置数据，byDataTitle为false生效
	             {
	                     id:'', //图标id
	                     tip:'', //提示内容
	                     triggerType:'', //触发方式  ‘mouseover’’、‘click’。默认‘mouseover’
	                     .....
	             }
	     ],

	     keyClass:'tooltip-icon',图标需包含类名 byDataTitle为true时可自定义类名，目的是区分已经绑定过click的'tooltip-icon'类名的提示
	     */
	bubbleTipsDomConfig: {
		allTipConfig: [], //当前页面提示框配置数据
		activeTipIconInfo: {}, //当前在显示的'click'类型提示 图标信息
		bindEventsByConfigFlag: false //当前页面是否执行过bubbleTips内bindEventsByConfig方法
	},
	bubbleTips(config, byDataTitle = false, keyClass = 'tooltip-icon') {
		let createUI = function() {
				if ($(".jk-addItem.jk-modeTypeTip").length) return;
				let style = document.createElement('link');
				style.href = '/common/style/bubbletip.css';
				style.rel = 'stylesheet';
				style.type = 'text/css';
				document.getElementsByTagName('head').item(0).appendChild(style);
				let html = `<div class="jk-addItem jk-modeTypeTip can-doc-copy">
                                <span class="tipsDetail"> </span>
                                <i class="bubble"></i>
                            </div> `
				$("body").append(html);
				// window.jkInitCopyCtrl?.(false);
			},
			changeClass = function($modeTypeTip, $lightPrompt) {
				$modeTypeTip.hide();
				$lightPrompt.removeClass("hideTip");
				$lightPrompt.addClass("showTip");
			},
			tipStyle = function(styleConfig) {
				let {
					defaultConfObj,
					$eventObj,
					$modeTypeTip,
					bubbleBottom,
					$bubble,
					$lightPrompt,
					keyClass = 'tooltip-icon'
				} = styleConfig;
				let {
					position,
					backgroundColor,
					borderColor,
					blockStyle,
					customClass
				} = defaultConfObj;
				let {
					width
				} = blockStyle;
				let {
					pageX,
					pageY
				} = position;
				let targetOffset = $eventObj.offset(),
					targetWidth = $eventObj.outerWidth(),
					targetHeight = $eventObj.outerHeight(),
					targetTop = targetOffset.top,
					targetLeft = targetOffset.left;

				width = width || $eventObj.attr('tip-width') || 200;
				$.extend(blockStyle, {
					width,
					backgroundColor
				})
				$modeTypeTip.css(blockStyle);
				let blockHeight = $modeTypeTip.outerHeight(),
					blockWidth = $modeTypeTip.outerWidth(),
					clientWidth = document.documentElement.clientWidth,
					countTopDis = targetTop - blockHeight - Math.abs(bubbleBottom),
					maxTopDis = pageY ? jkUtils.math.add(countTopDis, pageY) : (countTopDis - targetHeight / 2),
					topDis = maxTopDis,
					leftDis = jkUtils.math.add(pageX, targetLeft) + targetWidth,
					maxLeftDis = leftDis + 5 + blockWidth;
				$modeTypeTip.removeClass('tip-right-bottom tip-left-top tip-left-bottom tip-right-top');
				if (maxTopDis < 0) {
					topDis = pageY ? jkUtils.math.add(targetTop, pageY) : (targetTop + targetHeight / 2);
					topDis += 14;
					if (maxLeftDis < clientWidth) { // 出现在右下角
						!customClass && $modeTypeTip.addClass('tip-right-bottom');
						leftDis = leftDis + 5;
					} else { // 出现在左下方
						!customClass && $modeTypeTip.addClass('tip-left-bottom');
						leftDis = targetLeft - blockWidth - pageX - 4;
					}
				} else {
					topDis += 5;
					if (maxLeftDis > clientWidth) { // 出现在左上方
						!customClass && $modeTypeTip.addClass('tip-left-top');
						leftDis = targetLeft - blockWidth - pageX - 2;
					} else {
						!customClass && $modeTypeTip.addClass('tip-right-top');
					}
				}
				if (customClass) {
					$modeTypeTip.removeClass('tip-center tip-left tip-right tip-bottom tip-top');
					$modeTypeTip.addClass(customClass);
				}
				$modeTypeTip.css({
					left: `${leftDis - 8}px`,
					top: `${topDis}px`,
				});

				$bubble.css({
					borderColor: borderColor || ''
				});
				if (($eventObj.hasClass(keyClass) && ($eventObj.hasClass("showTip") || (!$eventObj.hasClass(
						"showTip") && !$eventObj.hasClass("hideTip"))))) {
					$modeTypeTip.show();
					$eventObj.removeClass("showTip").addClass("hideTip");
				}
			};

		let bindEventsByConfig = function(config) {
			if (config && Array.isArray(config) && config.length > 0) {
				let $modeTypeTip = $(".jk-addItem.jk-modeTypeTip"),
					$lightPrompt = $(".tooltip-icon");
				config.forEach(item => {
					if (!item.id) return;
					let hadItem = jkUtils.bubbleTipsDomConfig.allTipConfig.find(_ => _.id === item.id);
					if (hadItem) {
						$.extend(hadItem, item);
					} else {
						jkUtils.bubbleTipsDomConfig.allTipConfig.push($.extend({
							triggerType: 'mouseover'
						}, item));
					}
				})

				let callback = function(e, triggerType) {
					$lightPrompt = $(".tooltip-icon")
					let $eventObj = $(e.target), //事件对象
						$eventId = e.target.id, //事件对象id
						configObj = jkUtils.bubbleTipsDomConfig.allTipConfig.find(item => item.id ===
							$eventId && item.triggerType === triggerType), //过滤出当前对象数据
						defaultConfObj = {
							position: {
								pageX: 6
							},
							blockStyle: {}
						},
						$bubble = $(".jk-addItem .bubble");
					if (configObj) {
						configObj.triggerType == 'mouseover' && $modeTypeTip.addClass("hover-place");
						$modeTypeTip.removeClass(
							'tip-right-bottom tip-left-top tip-left-bottom tip-right-top');
						$modeTypeTip.addClass('tip-right-top');
						let bubbleBottom = parseFloat($bubble.css('bottom'));
						$.extend(defaultConfObj, configObj);
						$(".tipsDetail").html(defaultConfObj.tip);

						function stopPropagationClick(event) {
							event.stopPropagation()
						}
						if (triggerType === 'mouseover') {
							$eventObj.unbind("click", stopPropagationClick)
							$eventObj.bind("click", stopPropagationClick)
						}

						tipStyle({
							defaultConfObj,
							$eventObj,
							$modeTypeTip,
							bubbleBottom,
							$bubble,
							$lightPrompt
						});
						triggerType === 'click' && (jkUtils.bubbleTipsDomConfig.activeTipIconInfo = {
							iconEl: e.target,
							iconPrevInfo: mini.getXY(e.target)
						})
					}
				}

				let bindEvent = function() {
					if (jkUtils.bubbleTipsDomConfig.bindEventsByConfigFlag) return;
					$(document).unbind('mouseup.common-tooltip');
					$(document).on('mouseup.common-tooltip', function(e) {
						var $target = $(e.target);
						$lightPrompt = $(".tooltip-icon");
						if (!$target.is('.tooltip-icon')) {
							changeClass($modeTypeTip, $lightPrompt);
							jkUtils.bubbleTipsDomConfig.activeTipIconInfo = {};
						}
					})

					// mouseover
					$(document).unbind('mouseover.common-tooltip');
					$(document).unbind('mouseout.common-tooltip');
					$(document).on('mouseover.common-tooltip', '.tooltip-icon', function(e) {
						callback(e, 'mouseover')
					});
					$(document).on('mouseout.common-tooltip', '.tooltip-icon', function(e) {
						let configObj = jkUtils.bubbleTipsDomConfig.allTipConfig.find(item => item
							.id === e.target.id && item.triggerType === 'mouseover'); //过滤出当前对象数据
						const isInside = $modeTypeTip[0].contains(e.relatedTarget);
						if (configObj && !isInside) {
							changeClass($modeTypeTip, $lightPrompt);
						}
					});

					// click
					$(document).unbind('click.common-tooltip');
					$(document).on('click.common-tooltip', jkUtils.throttle(function(e) {
						if (!$(e.target).is('.tooltip-icon') || ($(e.target).is(
								'.tooltip-icon') && $(e.target).hasClass(
							'hideTip'))) { //点击其他区域及已经显示提示框的图标，隐藏提示框
							changeClass($modeTypeTip, $lightPrompt);
							jkUtils.bubbleTipsDomConfig.activeTipIconInfo = {};
							return;
						}

						callback(e, 'click')
					}, 200));
					$('.tooltip-icon-stopPropagation').unbind(
						'click.common-tooltip'); //带有’tooltip-icon-stopPropagation‘类名的图标点击事件阻止冒泡
					$('.tooltip-icon-stopPropagation').on('click.common-tooltip', function(e) {
						e.stopPropagation();
						e.preventDefault();
						if (($(e.target).is('.tooltip-icon') && $(e.target).hasClass(
								'hideTip'))) { //点击已经显示提示框的图标，隐藏提示框
							changeClass($modeTypeTip, $lightPrompt);
							jkUtils.bubbleTipsDomConfig.activeTipIconInfo = {};
							return;
						}
						callback(e, 'click')
					})
					// 页面滚动 隐藏提示框
					$(document).unbind('mousewheel.common-tooltip');
					$(document).on('mousewheel.common-tooltip', function() {
						try {
							let {
								iconEl,
								iconPrevInfo
							} = jkUtils.bubbleTipsDomConfig.activeTipIconInfo;
							if (iconEl && iconPrevInfo) {
								setTimeout(() => {
									let nowIconInfo = mini.getXY(iconEl);
									if (iconPrevInfo[0] != nowIconInfo[0] || iconPrevInfo[
											1] != nowIconInfo[1]) {
										changeClass($modeTypeTip, $lightPrompt);
										jkUtils.bubbleTipsDomConfig.activeTipIconInfo = {};
									}
								}, 200)
							}
						} catch (err) {}
					})
					$(document).unbind('mouseleave.hover-place');
					$(document).on('mouseleave.hover-place', ".jk-addItem.jk-modeTypeTip.hover-place",
						function() {
							changeClass($modeTypeTip, $lightPrompt);
						})
				}
				bindEvent();
				jkUtils.bubbleTipsDomConfig.bindEventsByConfigFlag = true;
			}
		}

		let bindEventsByDataTitle = function(keyClass = 'tooltip-icon') {
			let classStr = '.' + keyClass;
			let $modeTypeTip = $(".jk-addItem.jk-modeTypeTip");
			let $lightPrompt = $(classStr);

			$(document).unbind(`mouseup${classStr}`);
			$(document).on(`mouseup${classStr}`, function(e) {
				var $target = $(e.target);
				$lightPrompt = $(classStr)
				if (!$target.is(classStr)) {
					changeClass($modeTypeTip, $lightPrompt);
				}
			});

			let callback = function(e) {
				if (!$(e.target).is(classStr) || !$(e.target).attr('data-title')) return
				$lightPrompt = $(classStr)
				let $eventObj = $(e.target); //事件对象
				let configObj = {
					tip: $eventObj.attr('data-title')
				};
				let defaultConfObj = {
					position: {
						pageX: 6
					},
					blockStyle: {}
				}
				let $bubble = $(".jk-addItem .bubble");
				$modeTypeTip.removeClass('tip-right-bottom tip-left-top tip-left-bottom tip-right-top');
				$modeTypeTip.addClass('tip-right-top');
				let bubbleBottom = parseFloat($bubble.css('bottom'));
				$.extend(defaultConfObj, configObj);
				defaultConfObj.triggerType != 'click' && $modeTypeTip.addClass("hover-place");
				$(".tipsDetail").html(defaultConfObj.tip);

				function stopPropagationClick(event) {
					event.stopPropagation();
					event.preventDefault();
				}
				$eventObj.unbind("click", stopPropagationClick)
				$eventObj.bind("click", stopPropagationClick)

				tipStyle({
					defaultConfObj,
					$eventObj,
					$modeTypeTip,
					bubbleBottom,
					$bubble,
					$lightPrompt,
					keyClass
				});
			}

			$(document).unbind(`mouseover${classStr}`);
			$(document).unbind(`mouseout${classStr}`);
			$(document).on(`mouseover${classStr}`, classStr, jkUtils.throttle(function(e) {
				callback(e, 'mouseover')
			}, 200));
			$(document).on(`mouseout${classStr}`, classStr, function(e) {
				const isInside = $modeTypeTip[0].contains(e.relatedTarget);
				!isInside && changeClass($modeTypeTip, $lightPrompt);
			});
			$(document).unbind('mouseleave.hover-place');
			$(document).on('mouseleave.hover-place', ".jk-addItem.jk-modeTypeTip.hover-place", function() {
				changeClass($modeTypeTip, $lightPrompt);
			})
		}

		createUI();
		byDataTitle ? bindEventsByDataTitle(keyClass) : bindEventsByConfig(config);
	},
	// 获取已配置"页面模板"的菜单
	getAllMenuConfig() {
		jkUtils.localStorage.remove('allMenuConfig', 'commonData');
		jkUtils.jkAjax({
			url: '/jkyun/ccs/config/template/allMenuConfig',
			type: 'get',
			forceUseErrorHandler: false
		}).then(function(result, msg, code) {
			if (code == 200) {
				jkUtils.localStorage.set({
					key: 'allMenuConfig',
					value: JSON.stringify(result.data || []),
					namespace: 'commonData'
				});
			}
		})
	},

	//  根据业务组组别获取页面公共配置
	//  groupType:业务组组别，eg: OMS 、ERP ......
	getCommonIndustryConfig(groupType) {
		if (!groupType) return null;
		let allMenuConfigData = jkUtils.localStorage.get('allMenuConfig', 'commonData');
		if (!allMenuConfigData) return null;
		try {
			allMenuConfigData = JSON.parse(allMenuConfigData);
			let commonData = allMenuConfigData.common;
			if (commonData && jkUtils.jkIsObject(commonData)) {
				let groupData = commonData[groupType];
				return groupData ? JSON.parse(groupData) : null;
			} else {
				return null;
			}
		} catch (e) {
			return null;
		}
	},

	// 获取菜单“页面模板”详情数据
	getMenuCongigDetail(moduleCode, pageConfigData) {
		let promise = new Promise((resolve) => {
			if (!moduleCode) {
				resolve(null);
				return;
			}

			if (pageConfigData) { //返回预览模板数据
				resolve(pageConfigData);
				return;
			}

			let allMenuConfig = jkUtils.localStorage.get(
					"allMenuConfig",
					"commonData"
				),
				allMenuConfigData = [];
			if (!allMenuConfig) {
				resolve(null);
				return;
			}

			try {
				allMenuConfig = JSON.parse(allMenuConfig);
				allMenuConfigData = allMenuConfig.configs;
			} catch (e) {
				resolve(null);
				return;
			}
			if (allMenuConfigData && Array.isArray(allMenuConfigData) && allMenuConfigData.length) {
				let currentMenu = allMenuConfigData.find((item) => item.menuCode === moduleCode);
				if (!currentMenu) {
					resolve(null);
					return;
				}
				const {
					menuCode,
					value,
					version
				} = currentMenu;
				let params = {
					industryCode: allMenuConfig.industryCode,
					menuCode,
					value
				}
				// if (isInCef && window.jk_get_menu_config) {
				// 	params.version = version;
				// 	let configData = window.jk_get_menu_config(JSON.stringify(params));
				// 	try {
				// 		let data = JSON.parse(configData);
				// 		resolve(data);
				// 	} catch (e) {
				// 		resolve(null);
				// 	}
				// } else {
				// 	jkUtils.jkAjax({
				// 		url: "/jkyun/ccs/config/template/getConfigDate",
				// 		type: "get",
				// 		data: params
				// 	}).done((result, msg, code) => {
				// 		if (code === 200) {
				// 			try {
				// 				let data = JSON.parse(result.data);
				// 				resolve(data);
				// 			} catch (e) {
				// 				resolve(null);
				// 			}
				// 		}
				// 	}).fail(() => {
				// 		resolve(null);
				// 	});
				// }

			} else {
				resolve(null);
				return;
			}
		})
		return promise;
	},

	// 获取菜单下所有字段说明配置
	getFieldDescConfig(moduleCode) {
		return new Promise((resolve) => {
			if (!moduleCode) {
				resolve([]);
				return;
			}
			jkUtils.jkAjax({
				url: "/jkyun/ccs/fieldDesc/queueAllDesc",
				type: "get",
				data: {
					moduleCode
				}
			}).done((result, msg, code) => {
				if (code === 200) {
					try {
						resolve(result.data || []);
					} catch (e) {
						resolve([]);
					}
				} else {
					resolve([]);
				}
			}).fail(() => {
				resolve([]);
			});
		})
	},

	// 编辑字段说明会员标签
	permissionAddFieldDesc() {
		try {
			let loginInfo = topWindow.loginInfo || jkUtils.getLoginInfo();
			const tags = loginInfo.loginResult.tags;
			if (!tags) return false;
			return tags.includes('ccs_column_remark_manager');
		} catch (e) {
			return false;
		}
	},

	// 添加自定义字段说明相关操作
	editFieldDescOperation: {
		editDescWin: mini.get("jk_edit_field_desc_window"), //字段说明编辑弹框
		editDescRow: null, //编辑行
		editDescGrid: null, //编辑表格
		actionName: '', //与字段标识拼接，避免页面内不同组件的字段标识重复，表格列配置取值gridCode，自定义表单组件取值actionName
		fieldName: '', //字段标识取值字段
		init: function(actionName, fieldName = 'header') {
			let moduleInfo = jkUtils.jkGetModuleInfo();
			if (!moduleInfo) return;
			jkUtils.editFieldDescOperation.actionName = actionName;
			jkUtils.editFieldDescOperation.fieldName = fieldName;
			!Array.isArray(moduleInfo.fieldDescConfig) && (moduleInfo.fieldDescConfig = []);
		},
		loadEditWindowJS: function() {
			// if (!window.JKControl.JKFroalaEditor) {
			// 	let csssUrlList = [
			// 		"/component/froala/css/froala_style.min.css",
			// 		"/component/froala/css/froala_editor.pkgd.min.css",
			// 	];
			// 	csssUrlList.forEach((url) => {
			// 		let link = document.createElement("link");
			// 		link.rel = "stylesheet";
			// 		link.type = "text/css";
			// 		link.href = url;
			// 		document.querySelector("head").appendChild(link);
			// 	});
			// 	// 引入js，添加弹框
			// 	jkUtils.loadJs('/component/froala/js/froala_editor.pkgd.min.js', function() {
			// 		jkUtils.loadJs('/component/froala/froalaEditor.js', function() {
			// 			jkUtils.loadJs('/jk_mini/jk_mini_froalaeditor.js', function() {
			// 				jkUtils.editFieldDescOperation.appendEditDeacWindow();
			// 			})
			// 		})
			// 	})
			// }
		},
		// 添加字段说明编辑弹框
		appendEditDeacWindow: function() {
			let windowEl = `<div id='jk_edit_field_desc_window' class="mini-window" style='display:none;width:300px;height:340px;'
            showCloseButton="false" showModal="true" allowDrag="false">
              <div>
                <textarea id='jk-field-desc' class="jk-froala-edit" toolbarContainer="#toolButton" placeholder="请输入字段说明信息，可以粘贴图片"  microType='message' showAtBoxFlag='false' fontSizeDefaultSelection="12">
                </textarea>
                <span id="jk-field-desc-view" class="fa fa-eye" title='点击预览'></span>
                <img id="jk-field-desc-clear" src='/styles/img/clearFormat.png' title='清除格式'>
                <div style="display:flex;margin-top:10px;margin-left:15px;">显示宽度：<div id="jk_field_desc_width" class="mini-radiobuttonlist main-plat-radiobuttonlist"
                repeatDirection="vertical" style="display: inline-block;"></div></div>
                <div class="jk-portlet-footer background-grey">
                  <a id="jk-field-desc-submit" class="mini-button">确定</a>
                  <a id="jk-field-desc-close" class="mini-button cancelbtn">取消</a>
                </div>
              </div>
            </div>
            <div class="jk-field-desc-view-content">
              <div class="jk-field-desc-view-value"></div>
              <i class="mini-icon mini-iconfont mini-tools-close" id='jk-field-desc-view-content-close'></i>
            </div>`
			$('body').append(windowEl);
			mini.parse('jk_edit_field_desc_window');
			setTimeout(() => {
				let $descWidth = mini.get('jk_field_desc_width');
				$descWidth.set({
					idField: 'id',
					textField: 'text',
					value: '200',
					data: [{
							id: '200',
							text: '200px(推荐)'
						},
						{
							id: '300',
							text: '300px'
						}
					]
				});
				$descWidth.un('valuechanged').on('valuechanged', function(e) {
					$('.jk-field-desc-view-content').css('width', e.value + 'px')
				})
				jkUtils.editFieldDescOperation.editDescWin = mini.get("jk_edit_field_desc_window");
				$('#jk-field-desc-close').off('click').on('click', function() {
					jkUtils.editFieldDescOperation.editDescWin.hide();
					$('.jk-field-desc-view-content').hide();
				})
				$("#jk-field-desc-submit").off('click').on('click', function() {
					$('.jk-field-desc-view-content').hide();
					let fieldDesc = mini.get('jk-field-desc').getValue(false),
						fieldConfig = jkUtils.editFieldDescOperation.getConfigFieldDesc(jkUtils
							.editFieldDescOperation.editDescRow.field);
					if (fieldConfig) { //已有说明
						if (fieldDesc) { //修改
							jkUtils.editFieldDescOperation.fieldDescAjax('update', fieldDesc,
								fieldConfig);
						} else { // 删除
							jkUtils.editFieldDescOperation.fieldDescAjax('delete', '',
								fieldConfig);
						}
					} else { //新增
						if (!fieldDesc) {
							return;
						}

						jkUtils.editFieldDescOperation.fieldDescAjax('add', fieldDesc);
					}
				})
				$("#jk-field-desc-clear").off('click').on('click', function() {
					let $editor = mini.get('jk-field-desc');
					let fieldDesc = $editor.getValue(false);
					fieldDesc = jkUtils.editFieldDescOperation.removeHtmlStyle(fieldDesc);
					$editor.setValue(fieldDesc);
				})

				$("#jk-field-desc-view-content-close").off('click').on('click', function() {
					$('.jk-field-desc-view-content').hide();
				})

				$('#jk-field-desc-view').off('click').on('click', function() {
					let $content = $('.jk-field-desc-view-content');
					if ($content.css('display') === 'none') {
						let value = mini.get('jk-field-desc').getValue(false);
						if (value) {
							$(".jk-field-desc-view-value").html(value)
							$content.css('width', mini.get('jk_field_desc_width').getValue() +
								'px').show();
						}
					} else {
						$content.hide();
					}
				})
				jkUtils.editFieldDescOperation.showEditDeacWindow();
			}, 100)
		},
		// 字段说明接口
		fieldDescAjax: function(operateType, fieldDesc = '', fieldConfig) {
			let moduleInfo = jkUtils.jkGetModuleInfo();
			if (!moduleInfo) return;
			if (fieldDesc.length > 600) {
				jkUtils.layer({
					state: "warning",
					content: "说明内容限制600个字符",
				});
				return;
			}
			let {
				actionName,
				editDescWin,
				editDescGrid,
				editDescRow,
				fieldName
			} = jkUtils.editFieldDescOperation;
			if (!actionName || !editDescGrid || !editDescRow || !fieldName) return;
			let {
				moduleCode,
				moduleName,
				moduleId
			} = moduleInfo;
			moduleCode = moduleCode || moduleId;
			let url = '/jkyun/ccs/fieldDesc/delete',
				fieldCode = actionName + '_' + editDescRow.field,
				params = {
					moduleCode,
					fieldCode,
				};
			let descWidth = mini.get('jk_field_desc_width').getValue();
			if (operateType === 'add') {
				url = '/jkyun/ccs/fieldDesc/add';
				params = {
					moduleCode,
					moduleName,
					fieldCode,
					fieldName: editDescRow[fieldName],
					fieldDesc,
					descWidth
				}
			} else if (operateType === 'update') {
				url = '/jkyun/ccs/fieldDesc/update';
				params = {
					moduleCode,
					fieldCode: fieldConfig.fieldCode,
					fieldName: editDescRow[fieldName],
					fieldDesc,
					descWidth
				}
			}
			jkUtils.jkAjax({
				url,
				type: "post",
				data: params
			}).then((result, msg, code) => {
				if (code == 200) {
					jkUtils.layer({
						content: operateType === 'add' ? '新增成功' : '更新成功',
					});

					if (operateType === 'add') {
						moduleInfo.fieldDescConfig.push({
							fieldCode,
							fieldDesc,
							descWidth
						})
					} else if (operateType === 'update') {
						fieldConfig.fieldDesc = fieldDesc;
						fieldConfig.descWidth = descWidth;
					} else {
						moduleInfo.fieldDescConfig = moduleInfo.fieldDescConfig.filter(_ => _
							.fieldCode !== fieldConfig.fieldCode);
					}
					//更新提示图标
					editDescGrid && editDescGrid.updateRow(editDescRow);
				}
			}).always(() => {
				editDescWin.hide();
			})
		},
		// 显示字段说明编辑弹框
		showEditDeacWindow: function() {
			let descEditor = mini.get('jk-field-desc');
			let {
				editDescWin,
				editDescRow
			} = jkUtils.editFieldDescOperation;
			if (!descEditor || !editDescWin || !editDescRow) return;
			let config = jkUtils.editFieldDescOperation.getConfigFieldDesc(editDescRow.field);
			editDescWin.showAtPos("center", "middle");
			let desc = config ? config.fieldDesc || '' : '';
			descEditor.setValue(desc);
			descEditor.focus();
			mini.get('jk_field_desc_width').setValue((config && config.descWidth) ? config.descWidth : '200');
		},
		// 根据字段field值获取说明配置
		getConfigFieldDesc: function(field) {
			let moduleInfo = jkUtils.jkGetModuleInfo();
			let {
				actionName
			} = jkUtils.editFieldDescOperation;
			if (!moduleInfo || !actionName) return;
			let config;
			Array.isArray(moduleInfo.fieldDescConfig) && (config = moduleInfo.fieldDescConfig.find(item => item
				.fieldCode === actionName + '_' + field))
			return config;
		},
		removeHtmlStyle: function(fieldDesc) {
			let relStyle = /style\s*?=\s*?([‘"])[\s\S]*?\1/g,
				relClass = /class\s*?=\s*?([‘"])[\s\S]*?\1/g,
				relHtml = /<(?!\/?img\b|\/?br\b|\/?p\b|\/?div\b)[^>]+>/gi;

			fieldDesc = fieldDesc.replace(relHtml, '').replace(relStyle, '').replace(relClass, '');
			return fieldDesc;
		},
	},

	// 关闭tab前判断是否有tab处于编辑状态并提示
	beforeRemoveTab(tabsArr, removeCallBack, currentTabFlag = false) {
		if (!jkUtils.jkIsArray(tabsArr)) {
			callback();
			return;
		}
		let tipTabPromiseArr = tabsArr.map(tab => jkUtils.createAndDispatchEvent('tab-before-remove-tip', tab));
		Promise.all(tipTabPromiseArr).then(function() {
			let tabEditPool = topWindow.jkMainFramePage.tabEditPool || [],
				workTabs = [];
			if (tabEditPool.length) {
				workTabs = tabsArr.filter(_ => {
					let workTab = tabEditPool.find(t =>
						t.moduleId === _?.moduleInfo?.moduleId &&
						t.cloneIndex === _.cloneIndex
					);
					return workTab;
				})
			}
			let worksNum = workTabs.length;
			if (worksNum) {
				let html = "";
				for (let index = 0; index < workTabs.length; index++) {
					if (index > 2) {
						html += `<p class='jk-isEdit-tab-more'>......</p>`;
						break;
					};
					html +=
						`<p class='jk-isEdit-tab-info'>${index + 1}、“${workTabs[index].title}” 页面正在编辑...</p>`;
				}

				let message = currentTabFlag ? `您当前页面正在编辑中，确定要关闭吗？<br />${html}` :
					`您有${worksNum}个页面正在编辑，确定要关闭吗？<br />${html}`;
				mini.MessageBox.show({
					width: 400,
					maxWidth: 400,
					height: 260,
					title: '提示',
					buttons: ['ok', 'cancel'],
					message: message,
					iconCls: 'mini-messagebox-question',
					callback: function(action) {
						if (action === 'ok') {
							callback();
						}
					}
				});
			} else {
				callback();
			}
		})

		function callback() {
			jkUtils.jkIsFunction(removeCallBack) && removeCallBack();
		}
	},

	// 将形如 "2024-07-24 13:59:59" 的东八区时间字符串转为本地时区时间
	getLocalTimeByBeiJingTime(inputTimeStr, type = 'DateTime') {
		if (!inputTimeStr) return '';
		if (!jkUtils.jkIsString(inputTimeStr)) return jkUtils.formatTimeStamp(inputTimeStr, type);
		let localTime = '';
		try {
			localTime = new Date(inputTimeStr + " +0800");
			localTime = jkUtils.formatTimeStamp(localTime, type);
		} catch (e) {
			localTime = jkUtils.formatTimeStamp(inputTimeStr, type);
		}

		return localTime;
	},

	// 识别字符串内的http链接，转换成a链接
	strToAlink(str) {
		let result = '';
		if (str.indexOf('<a') >= 0 || str.indexOf('<img') >= 0 || str.indexOf('<video') >= 0 || str.indexOf(
				'<audio') >= 0) {
			result = str;
		} else {
			var reg =
				/(http:\/\/|https:\/\/|ftp:\/\/|www\.)[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?/g;
			//正则替换
			str = str.replace('&nbsp;', ' ').replace(reg, function(a) {
				//如果包含http ，indexOf方法如果包含返回0,所以加上!
				if (!a.indexOf('http')) {
					return '<a href="' + a + '" target=_blank>' + a + '</a>';
				} else {
					return '<a href="http://' + a + '" target=_blank>' + a + '</a>';
				}
			});
			result = str
		}
		return result
	},
	// 表格开启水印会员标签
	isgridWaterMask: function() {
		try {
			let loginInfo = topWindow.loginInfo || jkUtils.getLoginInfo();
			const tags = loginInfo.loginResult.tags
			if (!tags) return false;
			return tags.includes('grid_watermark');
		} catch (e) {
			return false;
		}
	},

	watermark: {
		defaultConfig: {
			container: document.body, //要插入水印的元素
			content: '', //水印内容
			width: 250, // canvas 画布宽度
			height: 150, // canvas 画布高度
			fillStyle: '#7d8285', // canvas 内部描述颜色
			alpha: 0.18, // 图形透明度, 范围 [0, 1]
			x: 10, // 水平方向 的移动距离
			y: 30, // 垂直方向 的移动距离
			top: 0, // 水印元素相对父元素的顶部位置
			left: 0, // 水印元素相对父元素的左侧位置
			font: 'microsoft yahei', // 字体
			fontSize: 16, //字体大小
			textAlign: 'left', //文本对齐  "left" || "right" || "center" || "start" || "end"
			textBaseline: 'top', //文本垂直对齐方式  "top" || "hanging" || "middle" || "alphabetic" || "ideographic" || "bottom";
			angle: -20, //旋转角度
			zIndex: 999 //水印层级
		},
		// 为元素添加水印
		createWatermark: function(config) {
			jkUtils.watermark.defaultConfig.content = topWindow.userinfo.realName + ' ' + topWindow.userinfo
				.userName;
			let watermarkConfig = Object.assign({}, jkUtils.watermark.defaultConfig, config),
				canvas = jkUtils.watermark._createCanvas(watermarkConfig);
			jkUtils.watermark._renderWatermark(canvas, watermarkConfig);
		},
		_createCanvas: function(watermarkConfig) {
			const {
				width,
				height,
				content
			} = watermarkConfig;
			let canvas = document.createElement('canvas');

			jkUtils.watermark._setConfigForCanvas(canvas,
				watermarkConfig); // <- 如果不设置配置里的字体, 无法正确拿到 content 在 canvas 中的长度
			const contentLen = canvas.getContext('2d').measureText(content).width;
			if (jkUtils.watermark._isTextOverflow(contentLen, watermarkConfig)) {
				const {
					width,
					height
				} = jkUtils.watermark._computedCanvasSizeWhenOverflow(contentLen, watermarkConfig);
				canvas.setAttribute('width', width);
				canvas.setAttribute('height', height);
			} else {
				canvas.setAttribute('width', width);
				canvas.setAttribute('height', height);
			}
			jkUtils.watermark._setConfigForCanvas(canvas, watermarkConfig);
			return canvas;
		},
		_renderWatermark: function(canvas, watermarkConfig) {
			const base64Url = canvas.toDataURL();
			const {
				container,
				zIndex,
				top,
				left,
				className
			} = watermarkConfig;

			const watermarkDiv = document.createElement('div');
			watermarkDiv.setAttribute('class', className);
			watermarkDiv.setAttribute('style', `
            position: absolute;
            top: ${top}px;
            left: ${left}px;
            width: 100%;
            height: 100%;
            z-index: ${zIndex};
            pointer-events: none;
            background-image: url('${base64Url}');
        `)

			if (Array.isArray(container)) {
				for (let i = 0, len = container.length; i < len; i++) {
					const watermarkClone = watermarkDiv.cloneNode(true);
					const dom = container[i];
					dom.style.position = 'relative';
					dom.insertBefore(watermarkClone, dom.firstChild);
				}
			} else {
				container.style.position = 'relative';
				container.insertBefore(watermarkDiv, container.firstChild);
			}
		},
		_isTextOverflow: function(contentLen, watermarkConfig) {
			// 文本是否溢出 canvas 画布
			const {
				witdh,
				x,
				y,
				angle
			} = watermarkConfig;
			const isOverflow =
				Math.abs(jkUtils.watermark._getSideBySin(angle, contentLen)) > y ||
				Math.abs(jkUtils.watermark._getSideByCos(angle, contentLen)) > (witdh - x);

			return isOverflow;
		},
		_setConfigForCanvas: function(canvas, watermarkConfig) {
			const {
				width,
				height,
				content,
				x,
				y,
				font,
				fontSize,
				textAlign,
				fillStyle,
				alpha,
				angle
			} = watermarkConfig;
			const ctx = canvas.getContext('2d');
			ctx.clearRect(0, 0, width, height);
			ctx.textBaseline = 'top';
			ctx.textAlign = textAlign;
			ctx.font = `${fontSize}px ${font}`;
			ctx.fillStyle = fillStyle;
			ctx.globalAlpha = alpha;

			const contentLen = ctx.measureText(content).width;
			jkUtils.watermark._isTextOverflow(contentLen, watermarkConfig) ?
				ctx.translate(
					x,
					Math.abs(jkUtils.watermark._getSideBySin(angle, contentLen)) + y
				) :
				ctx.translate(x, y)

			ctx.rotate(jkUtils.watermark._getRadianByAngle(angle));
			ctx.fillText(content, x, y);
		},
		_getRadianByAngle: function(angle) { // 根据角度计算出弧度
			return Math.PI / 180 * angle;
		},

		_getSideBySin: function(angle, len) { // 根据 正弦与斜边 获取 对边
			return Math.sin(jkUtils.watermark._getRadianByAngle(angle)) * len;
		},

		_getSideByCos: function(angle, len) { // 根据 余弦与斜边 获取 临边
			return Math.cos(jkUtils.watermark._getRadianByAngle(angle)) * len;
		},
		_computedCanvasSizeWhenOverflow: function(contentLen, watermarkConfig) {
			// 当文本溢出的时候重新计算 canvas 的大小
			const {
				height,
				x,
				y,
				fontSize,
				angle
			} = watermarkConfig;
			const realCanvasHeight = Math.abs(jkUtils.watermark._getSideBySin(angle, contentLen)) + (height -
				y);
			const realCanvasWidth = Math.abs(jkUtils.watermark._getSideByCos(angle, contentLen)) + x;

			return {
				height: Math.ceil(realCanvasHeight),
				width: Math.ceil(realCanvasWidth) + fontSize * 2
			}
		}
	},

	// 表格右键复制单号点击回调
	miCopyOrderNumber: function(grid, menu) {
		if (!grid || !menu || grid.excelSelectRange) return;
		let cell = menu.currentCell;
		if (cell !== null) {
			let row = cell[0],
				column = cell[1],
				context = grid.getColumnContext(row, column, true);
			const regex = /([0-9a-zA-Z])(?:[0-9a-zA-Z_-]+-){0,2}([0-9a-zA-Z_]{4,})|([0-9]{5,})/g;
			const numbersAndLetters = context.match(regex);
			let orderText = numbersAndLetters ? numbersAndLetters.join(' ') : '';
			jkUtils.jkCopy(orderText);
		}
	},

	/**
	 * 查询流转关系（自定义字段映射关系）
	 * @param {Object} params
	 * 'fromDocType' {String} 来源单据类型 必传  批量查询时，多个值以逗号分隔
	 * 'toDocType' {String} 目标单据类型 必传  批量查询时，多个值以逗号分隔
	 * 'defaultName' {Boolean} 是否默认使用名称模式, 默认false,不使用名称模式
	 * @param {Boolean} multiFlag  是否批量查询
	 */
	getCustomFieldMap: (params, multiFlag) => {
		return new Promise((resolve, reject) => {
			if (!jkUtils.jkIsObject(params)) {
				params = {};
			}
			let {
				fromDocType,
				toDocType
			} = params;
			if (!fromDocType || !toDocType) {
				reject('参数不能为空');
				return;
			}

			jkUtils.jkAjax({
				url: multiFlag ? '/jkyun/ccs/customField/batchFieldConvertMapping' :
					'/jkyun/ccs/customField/getFieldConvertMapping',
				type: 'get',
				data: params,
				localCache: true, //缓存接口数据
				cacheTimeStamp: 5 * 60 * 1000, //缓存接口数据 5分钟
				useErrorHandler: false
			}).then((result) => {
				resolve(result && result.data);
			}).fail((e) => {
				let res = null;
				try {
					let errObj = JSON.parse(e);
					if (errObj.code === 200) {
						res = errObj.result.data
					}
				} catch (err) {}
				resolve(res);
			});
		})
	},

	// 设置体积、重量列配置的值，进行表头筛选前，进行数值转换
	formatHeaderFilterValue(grid, e) {
		if (!grid?.businessColumns || !e?.field || !e?.condition?.value) return;
		const column = grid.businessColumns.find(item => item.field === e.field);
		const implementConver = (list) => { // 重量或体积转换方法
			return list.some(({
				condition,
				unit,
				type,
				baseUnit
			}) => {
				if (condition) {
					e.condition.value = jkUtils[type === 'volume' ? 'getVolumeValueByConfig' :
						'getWeightValueByConfig'](e.condition.value, unit, baseUnit, true);
					return true;
				}
				return false;
			})
		}
		const {
			volumeUnitFlag,
			weightUnitFlag,
			volumeUnit,
			weightUnit,
			weightBaseUnit,
			volumeBaseUnit
		} = column;
		const conversionList = [{
				condition: grid.configVolumeUnit && volumeUnitFlag,
				unit: volumeUnit,
				type: "volume",
				baseUnit: volumeBaseUnit
			}, // 体积
			{
				condition: grid.configWeightUnit && weightUnitFlag,
				unit: weightUnit,
				type: "weight",
				baseUnit: weightBaseUnit
			}, // 重量
		];
		if (implementConver(conversionList) || !topWindow.isInCef) return;
		const volumeGrobal = topWindow.localConfigs?.volumeConfig,
			weightGrobal = topWindow.localConfigs?.weightConfig;
		const {
			weightGlobalFlag,
			volumeGlobalFlag
		} = column;
		const conversionGlobalList = [{
				condition: grid.weightGlobalUnit && weightGlobalFlag,
				unit: weightGrobal,
				type: "weight",
				baseUnit: weightBaseUnit
			}, // 全局重量
			{
				condition: grid.volumeGlobalUnit && volumeGlobalFlag,
				unit: volumeGrobal,
				type: "volume",
				baseUnit: volumeBaseUnit
			}, // 全局体积
		];
		implementConver(conversionGlobalList)
	},
	// 设置体积、重量列配置的表格列，复制单元格时，直接复制单元格innerText
	// 使用：copyRenderer:jkUtils.weightAndVolumeCopyRenderer
	weightAndVolumeCopyRenderer(e) {
		if (!e) return '';
		let {
			sender,
			column,
			row
		} = e;
		let value = jkUtils.getDTOValue(row, column.displayField || column.field);
		let cellEl = sender.getCellEl(row, column);
		cellEl && (value = cellEl.innerText);
		return value;
	},

	// 根据配置，获取转换后的体积值
	getVolumeValueByConfig(val, configUnit, volumeBaseUnit, formatHeaderFilterFlag) {
		let value = val,
			countFlag, countType, countSecondValue = 1000000;
		let divStr = formatHeaderFilterFlag ? 'mul' : 'div',
			mulstr = formatHeaderFilterFlag ? 'div' : 'mul';
		switch (volumeBaseUnit) {
			case 'default': //以立方厘米为基础计算单位
				judgeCountFlag('cubicMeter', divStr);
				break;
			case 'cubicMeter': //以立方米为基础计算单位
				judgeCountFlag('default', mulstr);
				break;
			default:
				judgeCountFlag('cubicMeter', divStr);
		}

		function judgeCountFlag(unit, type) {
			if (configUnit === unit) {
				countFlag = true;
				countType = type;
			}
		}
		countFlag && (value = jkUtils.math[countType](val, countSecondValue));
		return value;
	},
	// 根据配置，获取转换后的重量值
	getWeightValueByConfig(val, configUnit, weightBaseUnit, formatHeaderFilterFlag) {
		let value = val,
			countFlag, countType, countSecondValue;
		let divStr = formatHeaderFilterFlag ? 'mul' : 'div',
			mulstr = formatHeaderFilterFlag ? 'div' : 'mul';
		switch (weightBaseUnit) {
			case 'default': //以g为基础计算单位
				judgeCountFlag('kg', divStr, 1000);
				!countFlag && judgeCountFlag('t', divStr, 1000000);
				break;
			case 'kg':
				judgeCountFlag('default', mulstr, 1000);
				!countFlag && judgeCountFlag('t', divStr, 1000);
				break;
			case 't':
				judgeCountFlag('default', mulstr, 1000000);
				!countFlag && judgeCountFlag('kg', mulstr, 1000);
				break;
			default:
				judgeCountFlag('kg', divStr, 1000);
				!countFlag && judgeCountFlag('t', divStr, 1000000);
		}

		function judgeCountFlag(unit, type, secondValue) {
			if (configUnit === unit) {
				countFlag = true;
				countType = type;
				countSecondValue = secondValue;
			}
		}
		countFlag && (value = jkUtils.math[countType](val, countSecondValue));
		return value;
	},

	// 截取指定字节的字符串并添加'...'，若str字节长度小于n直接返回str
	// str： 要截取的字符串  n: 保留的字节数
	sliceSpecifyLengthString(str, n = 12) {
		if (typeof str !== 'string') return str;
		let num = 0;
		let result = str;
		for (let i = 0; i < str.length; i++) {
			num += ((str.charCodeAt(i) > 255) ? 2 : 1);
			if (num > n) {
				result = str.substring(0, i);
				result += '...';
				break;
			}
		}
		return result;
	},
	showIntervalExport(data) {
		let {
			width,
			height,
			timeField,
			title,
			onSubmit
		} = data
		if (!timeField || !timeField.length) return jkUtils.layer({
			state: "warning",
			content: "请在左侧筛选中选择时间字段",
		});

		mini.open({
			targetWindow: window,
			url: '/jk_mini/export_interval/export_interval.html',
			title: title || topWindow.jkMainFramePage.i18n('new_export_interval', '新增定时导出任务', 'base'),
			width: width || 510,
			height: height || 545,
			allowResize: false,
			showModal: true,
			onload: function() {
				var iframe = this.getIFrameEl();
				iframe.contentWindow.setData(data);
			},
			ondestroy: function(message) {
				if (message === 'ok') {
					jkUtils.layer({
						content: '保存成功'
					})
					onSubmit && onSubmit()
				}
			}
		});
	},

	// ccs公共配置(同一吉客号下都能获取到该配置)
	cssCommonConfig: {
		// serviceType: String 配置key值
		// serviceGroup: String 配置分组 eg: OMS_xxx_xxx
		// configInfo: String 配置信息
		// configDesc: String 配置描述
		// 新增公共配置
		jkSaveServiceConfig: function(config = {}) {
			return new Promise((resolve, reject) => {
				let {
					serviceType,
					serviceGroup,
					configInfo,
					configDesc
				} = config;
				if (!serviceType || !serviceGroup || !configInfo) {
					reject('参数不能为空');
					return;
				};
				jkUtils.jkAjax({
					url: '/jkyun/ccs/serviceConfig/add',
					type: 'post',
					data: config,
				}).then((result) => {
					resolve(result);
				}).fail((e) => {
					reject(e);
				});
			})
		},
		// 更新公共配置
		jkUpdateServiceConfig: function(config) {
			return new Promise((resolve, reject) => {
				let {
					serviceType,
					serviceGroup,
					configInfo,
					configDesc
				} = config;
				if (!serviceType || !serviceGroup || !configInfo) {
					reject('参数不能为空');
					return;
				};
				jkUtils.jkAjax({
					url: '/jkyun/ccs/serviceConfig/update',
					type: 'post',
					data: config,
				}).then((result) => {
					resolve(result);
				}).fail((e) => {
					reject(e);
				});
			})
		},
		// 删除公共配置
		jkDeleteServiceConfig: function(config) {
			return new Promise((resolve, reject) => {
				let {
					serviceType
				} = config;
				if (!serviceType) {
					reject('参数不能为空');
					return;
				};
				jkUtils.jkAjax({
					url: '/jkyun/ccs/serviceConfig/deleteByServiceType',
					type: 'post',
					data: config,
				}).then((result) => {
					resolve(result);
				}).fail((e) => {
					reject(e);
				});
			})
		},
		// 根据分组查询公共配置
		jkGetServiceConfigList: function(config) {
			return new Promise((resolve, reject) => {
				let {
					serviceGroup
				} = config;
				if (!serviceGroup) {
					reject('参数不能为空');
					return;
				};
				jkUtils.jkAjax({
					url: '/jkyun/ccs/serviceConfig/selectByServiceGroup',
					type: 'get',
					data: config,
				}).then((result) => {
					resolve(result && result.data);
				}).fail((e) => {
					reject(e);
				});
			})
		},
		// 根据配置key值 查询公共配置详情
		jkGetServiceConfigDetail: function(config) {
			return new Promise((resolve, reject) => {
				let {
					serviceType
				} = config;
				if (!serviceType) {
					reject('参数不能为空');
					return;
				};
				jkUtils.jkAjax({
					url: '/jkyun/ccs/serviceConfig/selectByServiceType',
					type: 'get',
					data: config,
				}).then((result) => {
					resolve(result && result.data);
				}).fail((e) => {
					reject(e);
				});
			})
		},
	},
	// 流程步骤组件
	JKSteps: {
		/**
		 * 创建组件
		 * @param   {Object}  config  组件配置
		 * $containerEl 组件容器 
		 * stepsArr {Arrary} 流程配置 eg: [text:'任务1',bgClass:'bg-none'];
		 * clickCallback：function(stepItem,index)  {Function}  流程点击回调，stepItem：被点击流程配置；index: 被点击流程索引;stepClass: 被点击流程类名
		 */
		init: function(config) {
			if (!jkUtils.jkIsObject(config)) return;
			const {
				$containerEl,
				stepsArr,
				clickCallback
			} = config;
			if (!$containerEl || !Array.isArray(stepsArr) || !stepsArr.length) return;

			let $stepsEl = $(`<ul class="steps" style='display:block;overflow:hidden;'></ul>`);
			stepsArr.forEach((item, index) => {
				let $liEl = $(`<li data-index=${index}>
            <div class='arrow ${item.bgClass || 'bg-green'}'><span class='stepText'>${item.text}</span><div></div></div>
          </li>`);
				$stepsEl.append($liEl);
				if (jkUtils.jkIsFunction(clickCallback)) {
					$liEl.on('click', function() {
						let classDomList = $liEl.children('div');
						clickCallback({
							stepItem: item,
							index,
							stepClass: (classDomList.length && classDomList[0]
								.className) || ''
						})
					})
				}
			})
			$containerEl.append($stepsEl);
		},
		/**
		 * 修改$containerEl 容器内 第index个流程的内容 或 背景颜色
		 * @param   {Node}  $containerEl    流程组件容器
		 * @param   {Number}  index         第index个流程（index从0开始）
		 * @param   {String}  text          替换的内容
		 * @param   {String}  bgClass       类名
		 */
		changeStepStatus: function({
			$containerEl,
			index = 0,
			text = '',
			bgClass = ''
		}) {
			if (!$containerEl) return;
			text && $containerEl.find(`li[data-index=${index}] .stepText`).html(text);
			bgClass && $containerEl.find(`li[data-index=${index}] .arrow`).removeClass().addClass('arrow ' +
				bgClass);
		},
		/**
		 * 获取$containerEl 容器内 每个流程的类名
		 * @param   {Node}  $containerEl    流程组件容器
		 * @return  {Arrary}                返回由流程类名组成的数组
		 */
		getStepClass: function($containerEl) {
			if (!$containerEl) return [];
			let $liList = $containerEl.find('.steps li');
			if (!$liList.length) return [];
			let classList = [],
				$stepsContain;
			for (let i = 0; i < $liList.length; i++) {
				$stepsContain = $($liList[i]).children('div');
				classList.push(($stepsContain.length && $stepsContain[0].className) || '')
			}
			return classList;
		}
	},

	/**
	 * @description: 打开当前模块下的某个帮助文档
	 * @param {String} docId 文档id
	 */
	openHelpDoc(docId) {
		if (topWindow.customclient == 3) { // 个性化定制客户暂不能查看帮助文档
			jkUtils.showTip({
				content: "暂不支持查看",
				state: "warning",
			});
			return;
		}
		const info = jkUtils.jkGetModuleInfo();
		const moduleCode = (info) ? info.moduleId || info.moduleCode : '';
		topWindow.isInCef ? topWindow.jkMainFramePage.openMainTab({
			type: '',
			url: `helpcenter/index.html?refId=${moduleCode}&docId=${docId}`,
			title: jkUtils.jki18n('com_help_center', '帮助中心', 'common'),
			moduleId: moduleCode,
			isMultiWindow: true,
			isHelpCenter: true
		}) : mini.open({
			targetWindow: window,
			url: `helpcenter/index.html?refId=${moduleCode}&docId=${docId}`,
			title: jkUtils.jki18n('com_help_center', '帮助中心', 'common'),
			width: '95%',
			height: '92%',
			maxWidth: 1500,
			showHeader: true,
			showModel: true,
			allowResize: false,
			showCloseButton: true,
			allowDrag: true,
			onload: function() {},
			ondestroy: function() {}
		});
	},
	//快捷键设置
	keyboard: {
		// 初始化
		init: function() {
			var that = this;
			var localInfos = that.getLocalInfos(),
				defaultKeyInfos = mini.clone(that.defaultInfos);

			var businessLocalInfos = that.getLocalInfos('business'),
				businessDefaultKeyInfos = mini.clone(that.businessDefaultInfos);
			//获取当前的常用菜单快捷键
			jkUtils.jkGetCCSConfig('jkLocalInfos', 'commonMenu_keyboardInfos').then(infos => {
				let commonMenuLocalInfos = infos;
				if (!commonMenuLocalInfos || !commonMenuLocalInfos.length) {
					commonMenuLocalInfos = that.getLocalInfos('commonMenu');
				}
				commonMenuLocalInfos && that.checkKeyInfos(that.commonMenuInfos, commonMenuLocalInfos,
					'commonMenu');
			});

			localInfos && that.checkKeyInfos(defaultKeyInfos, localInfos);
			businessLocalInfos && that.checkKeyInfos(businessDefaultKeyInfos, businessLocalInfos, 'business');
			//设置当前的系统快捷键
			that.infos = defaultKeyInfos;
			//设置当前的业务快捷键
			that.businessInfos = businessDefaultKeyInfos;
		},
		//快捷键的funcCode和数量以当前的默认设置为准
		//检查保存的快捷键设置 和默认的快捷键设置是否匹配
		checkKeyInfos: function(defaultData, localData, type) {
			defaultData.forEach(info => {
				if (info.unEdit) return;
				var find = localData.find(local => local.funcCode === info.funcCode);
				if (find) {
					switch (type) {
						case 'business': //业务快捷键
							$.extend(info, {
								code: find.code,
								text: find.text,
								edit: find.edit
							})
							break;
						case 'commonMenu': //常用菜单快捷键
							const {
								code, text, moduleCode, moduleName, isNavMenu, parentId
							} = find;
							$.extend(info, {
								code,
								text,
								moduleCode,
								moduleName,
								isNavMenu,
								parentId
							})
							break;
						default: //系统快捷键
							$.extend(info, {
								code: find.code,
								text: find.text,
								edit: find.edit
							})
							break;
					}
				}
			});
		},
		// 保存快捷键设置
		setInfos: function(infos, type) {
			var that = this,
				json, key = 'common_keyboardInfos';
			if (!infos) return;
			switch (type) {
				case 'business': //业务快捷键
					that.businessInfos = infos;
					key = 'business_keyboardInfos';
					break;
				case 'commonMenu': //常用菜单快捷键
					that.commonMenuInfos = infos;
					key = 'commonMenu_keyboardInfos';
					jkUtils.jkSaveCCSConfig({
						moduleCode: 'jkLocalInfos',
						actionName: key,
						configs: infos
					})
					break;
				default: //系统快捷键
					that.infos = infos;
					break;
			}
			json = JSON.stringify(infos);
			localStorage.setItem(key, json);
			if (window.print_set_one)
				jkUtils.localConfig.setForModule(key, json); //from rong 壳内本地存储方法
		},
		//获取系统快捷键设置
		getInfos: function() {
			return this.infos;
		},

		/**
		 * @description: 获取业务组快捷键设置
		 * @param {String} moduleId 快捷键所属页面的moduleId
		 * @param {String} module 快捷键所属业务组 eg:oms
		 */
		getBusinessInfos: function(moduleId = '', module = '') {
			let moduleInfos = this.businessInfos;
			if (module) {
				moduleInfos = this.businessInfos.filter(item => item.module === module)
			}
			if (moduleId) {
				moduleInfos = moduleInfos.filter(item => item.moduleId === moduleId)
			}
			return moduleInfos;
		},
		// 获取常用菜单快捷键设置
		getCommonMenuInfos: function() {
			return this.commonMenuInfos;
		},

		// 获取本地快捷键设置
		getLocalInfos: function(type) {
			var json = null;
			var key = type ? type + '_keyboardInfos' : 'common_keyboardInfos';
			if (window.print_set_one) { //from rong 壳内本地存储方法
				json = jkUtils.localConfig.getByModule(key);
				if (json) return JSON.parse(json);
				return '';
			}
			json = localStorage.getItem(key);
			if (json) return JSON.parse(json);
			return '';
		},
		//恢复默认设置
		setDefault: function(type) {
			var json = '';
			var key = type === 'business' ? 'business_keyboardInfos' : 'common_keyboardInfos'
			localStorage.setItem(key, json);
			if (window.print_set_one)
				jkUtils.localConfig.setForModule(key, json); //from rong 壳内本地存储方法

			type === 'business' ? (this.businessInfos = mini.clone(this.businessDefaultInfos)) : (this.infos =
				mini.clone(this.defaultInfos));
		},
		// 快捷键默认设置(n:normal Code ;s:shift + code ;c:control + code ;a:alt + code)
		infos: [],
		businessInfos: [],
		commonMenuInfos: [{
				code: 'n113',
				funcCode: 12,
				text: 'F2',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n114',
				funcCode: 13,
				text: 'F3',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n115',
				funcCode: 14,
				text: 'F4',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n116',
				funcCode: 15,
				text: 'F5',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n117',
				funcCode: 16,
				text: 'F6',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n118',
				funcCode: 17,
				text: 'F7',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n119',
				funcCode: 18,
				text: 'F8',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n120',
				funcCode: 19,
				text: 'F9',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			},
			{
				code: 'n121',
				funcCode: 20,
				text: 'F10',
				moduleCode: '',
				moduleName: '',
				parentId: '',
				isNavMenu: false
			}
		],
		defaultInfos: [{
				code: 'c9',
				funcCode: 1,
				action: '切换至右侧标签',
				text: 'Ctrl + Tab',
				edit: true
			},
			{
				code: 'cb9',
				funcCode: 2,
				action: '切换至左侧标签',
				text: 'Ctrl + Shift + Tab',
				edit: true
			},
			{
				code: 'c84',
				funcCode: 3,
				action: '打开一个新的标签',
				text: 'Ctrl + T',
				edit: true
			},
			{
				code: 'c81',
				funcCode: 4,
				action: '切换至上次使用的标签',
				text: 'Ctrl + Q',
				edit: true
			},
			{
				code: 'c78',
				funcCode: 5,
				action: '在新窗口中打开',
				text: 'Ctrl + N',
				edit: true
			},
			{
				code: 'c82',
				funcCode: 6,
				action: '恢复上次关闭的标签',
				text: 'Ctrl + R',
				edit: true
			},
			{
				code: 'c72',
				funcCode: 7,
				action: '关闭当前页面',
				text: 'Ctrl + H',
				edit: true
			},
			{
				code: 'c75',
				funcCode: 8,
				action: '定位到搜索框',
				text: 'Ctrl + K',
				edit: true
			},
			{
				code: 'c76',
				funcCode: 9,
				action: '离开锁定',
				text: 'Ctrl + L',
				edit: true
			},
			{
				code: 'c77',
				funcCode: 10,
				action: '本地设置',
				text: 'Ctrl + M',
				edit: true
			}
		],
		businessDefaultInfos: [{
				code: 'c90',
				funcCode: 11,
				action: '销售单审核：执行审核',
				text: 'Ctrl + Z',
				edit: true, //是否启用
				module: 'oms', //所属业务组
				moduleId: 'order_auditv2'
			},
			{
				code: 'c73',
				funcCode: 22,
				action: '销售单审核：新增货品',
				text: 'Ctrl + I',
				edit: false,
				module: 'oms',
				moduleId: 'order_auditv2'
			},
			{
				code: 'c79',
				funcCode: 23,
				action: '销售单审核：替换货品',
				text: 'Ctrl + O',
				edit: false,
				module: 'oms',
				moduleId: 'order_auditv2'
			},
			{
				code: 'c80',
				funcCode: 24,
				action: '销售单审核：策略拆分',
				text: 'Ctrl + P',
				edit: false,
				module: 'oms',
				moduleId: 'order_auditv2'
			},
			{
				code: 'c83',
				funcCode: 21,
				action: '左侧筛选：筛选按钮',
				text: 'Ctrl + S',
				edit: false, //是否启用
				module: 'common', //公共
				moduleId: ''
			}
		],
		// 快捷键匹配
		match: function(codeInfo) {
			var that = this;
			var code = (codeInfo.altKey ? 'a' : (codeInfo.ctrlKey ? 'c' : (codeInfo.shiftKey ? 's' : 'n'))) +
				codeInfo.keyCode;
			var find = that.infos.find(i => i.funcCode === code);
			if (find)
				return find.funcCode;
			else
				return code;
		},
		//按字段匹配快捷键文字描述
		matchText: function(code, fieldName) {
			fieldName = fieldName || 'funcCode';
			var that = this;
			var find = that.infos.find(i => i[fieldName] === code);
			if (find)
				return find.text;
			else
				return '';
		},
		getKeyNameByKeyCode(keyCode, isChineseFirst) {
			let keyNameArr = jkEnums.keyCodeEnum;
			let find = keyNameArr.find(key => key.keyCode === parseInt(keyCode));
			if (find) return isChineseFirst ? (find.cname || find.keyName) : find.keyName;
			return '';
		}
	},
	// 检测会员标签 
	checkTag(tag) {
		let target = (topWindow.loginInfo || jkUtils.getLoginInfo() || {}).loginResult;
		if (!target || !Array.isArray(target.tags)) return false;
		return target.tags.includes(tag)
	},
	// erp校验工具
	erpVerifyUtils: {
		winConfig: {
			targetWindow: window,
			width: 900,
			height: 500
		},
		cache: {
			gspData: {},
			bactchData: {}
		},
		clearCache(type) {
			if (type === 'GSP') {
				this.cache.gspData = {}
			} else if (type === 'Batch') {
				this.cache.bactchData = {}
			} else {
				this.cache.gspData = {};
				this.cache.bactchData = {}
			}
		},
		/**
		 * 错误消息弹窗
		 * @param {object} option 
		 * @param {string } option.type 错误来源 [checkBatch  , checkGSP]
		 * @param {object} option.data 错误的数据
		 * @returns {promise} 返回两种状态[continue:继续， close:"关闭"]
		 */
		openErrorDialog(option) {
			let winConfig = this.winConfig
			return new Promise((resolve) => {
				mini.open({
					targetWindow: winConfig.targetWindow,
					url: '/gsp/common/check_dialog/index.html',
					title: "提示",
					width: winConfig.width,
					height: winConfig.height,
					showModel: true,
					showHeader: true,
					showMaxButton: true,
					allowDrag: true,
					onload: function() {
						let iframe = this.getIFrameEl()
						iframe.contentWindow.setWin.setData(option)
					},
					ondestroy: function(message) {
						resolve(message)
						return;
					}
				})
			})


		},
		getData(url, data) {
			return new Promise((resolve, reject) => {
				jkUtils.jkAjax({
						type: "post",
						url,
						data
					})
					.then(({
						data
					}) => resolve(data))
					.fail(err => reject(err))
			})
		},
		/**
		 * 批次校验
		 * @param {Object} BatchParam 传参参考 http://192.168.88.47/web/#/3/21588
		 * @returns {promise<object>}  .then 校验成功或者失败  .catch 其他错误原因
		 */
		async checkBatch(BatchParam, isShowDialog = true) {
			let result = {
				action: "success",
				step: "checkBatch",
				data: null
			};
			result.data = await this.getData("/jkyun/erp-goods/batch/validate", {
				param: BatchParam
			})
			if (!result.data) return result; // 无数据默认校验成功
			if (this.checkBatchData(result.data)) return result; // 数据校验成功；
			// 弹窗错误处理
			if (isShowDialog) {
				let action = await this.openErrorDialog({
					type: "checkBatch",
					data: _.cloneDeep(result.data)
				})
				result.action = action;
				return result
			}
			return result;
		},
		/**
		 * 检测批次校验的数据是否通过
		 * @param {Object} data 
		 * @returns {Boolean}
		 */
		checkBatchData(data) {
			const {
				productDateValidationResult, // 生产日期校验结果
				productDateValidation, //生产日期校验方式
				productDateValidationResultList,
				shelfLifeValidationResult, // 保质期校验结果
				shelfLifeValidation, //保质期校验方式
				shelfLifeValidationResultList,
			} = data;
			if (productDateValidationResult && shelfLifeValidationResult) return true;
			if (productDateValidation === 3 && shelfLifeValidation === 3) return true;
			if (
				productDateValidationResultList &&
				productDateValidationResultList.length &&
				productDateValidationResult === false &&
				productDateValidation !== 3
			) {
				return false;
			}
			if (
				shelfLifeValidationResultList &&
				shelfLifeValidationResultList.length &&
				shelfLifeValidationResult === false &&
				shelfLifeValidation !== 3

			) {
				return false;
			}
			return true;
		},
		/**
		 * GSP校验
		 * @param {Object} GSPParam 传参参考 http://192.168.88.47/web/#/137/21553
		 * @returns {promise<object>} .then 校验成功或者失败  .catch 其他错误原因
		 */
		async checkGSP(GSPParam, isShowDialog = true) {
			let result = {
				action: "success",
				step: "checkGSP",
				data: null
			}
			let isGspTag = jkUtils.checkTag('GSP'); // 检测是否gsp会员标签
			if (!isGspTag) return result;
			result.data = await this.getData('/jkyun/gsp/process/validate', {
				param: GSPParam
			})
			if (!result.data) return result;
			if (this.checkGSPdata(result.data)) return result; // 校验成功；
			if (isShowDialog) {
				let action = await this.openErrorDialog({
					type: "checkGSP",
					data: _.cloneDeep(result.data)
				})
				result.action = action;
				return result;
			}
			return result;
		},
		/**
		 * 检测Gsp校验结果是否通过
		 * @param {Object} data 
		 * @returns {boolean}
		 */
		checkGSPdata(data) {
			const {
				vendValidationResult, // 供应商资质校验结果 是否通过校验
				vendValidationResultList,
				goodsValidationResult, //货品资质校验结果 有三种状态 true , false , 或者 ‘’ 
				goodsValidationResultList,
				customerValidationResult, // 客户资质校验结果
				customerValidationResultList
			} = data;
			if (vendValidationResult && goodsValidationResult && customerValidationResult) return true;
			if (vendValidationResult === false && vendValidationResultList && vendValidationResultList.length) {
				let flag = vendValidationResultList.some(v => [1, 2].includes(v.vendValidation))
				if (flag) return false;
			}
			if (goodsValidationResult === false && goodsValidationResultList && goodsValidationResultList.length) {
				let flag = goodsValidationResultList.some(v => [1, 2].includes(v.goodsValidation))
				if (flag) return false;
			}
			if (customerValidationResult === false && customerValidationResultList && customerValidationResultList
				.length) {
				let flag = customerValidationResultList.some(v => [1, 2].includes(v.customerValidation))
				if (flag) return false
			}
			return true;
		},
		/**
		 * 检测当前页面是否校验批次
		 * @param {String} businessType 单据类型
		 * @param {String} validationTiming 校验时机
		 * @returns {boolean} 是否校验 true 校验 false 不校验
		 */
		async isOpenBacth(businessType, validationTiming) {
			let cacheData = this.cache.bactchData[businessType]
			if (cacheData) {
				let target = cacheData.find(v => v.validationTiming === validationTiming)
				return target ? Number(target.isOpen) === 1 : false; //找不到默认不校验
			}
			cacheData = await this.getData('/jkyun/erp-goods/batch/validation/config/getByBusinessType', {
				businessType
			})
			this.cache.bactchData[businessType] = cacheData || []
			return this.isOpenBacth(businessType, validationTiming)
		},
		/**
		 * 检测当前页面是否校验GSP
		 * @param {String} businessType 单据类型
		 * @param {String} validationTiming 校验时机
		 * @returns {boolean} 是否校验 true 校验 false 不校验
		 */
		async isOpenGSP(businessType, validationTiming) {
			if (!jkUtils.checkTag('GSP')) return false;
			let cacheData = this.cache.gspData[businessType]
			if (cacheData) {
				let target = cacheData.find(v => v.validationTiming === validationTiming)
				return target ? Number(target.isOpen) === 1 : false //找不到默认不校验
			}
			cacheData = await this.getData('/jkyun/gsp/open/process/config/getByBusinessType', {
				businessType
			})
			this.cache.gspData[businessType] = cacheData || [];
			return this.isOpenGSP(businessType, validationTiming)
		},
		/**
		 * 检测当前页面的校验类型
		 * @param {String} businessType 
		 * @param {string} validationTiming 
		 * @returns {Number} 0 不校验 1 只批次校验 2 只gsp校验 3 批次校验和GSP校验
		 */
		async getValidationType(businessType, validationTiming) {
			let batchFlag = await this.isOpenBacth(businessType, validationTiming)
			let gspFlag = await this.isOpenGSP(businessType, validationTiming)
			if (!batchFlag && !gspFlag) return 0
			else if (batchFlag && !gspFlag) return 1
			else if (!batchFlag && gspFlag) return 2
			else return 3
		}
	}
};
if (jkUtils.isInDev()) {
	delete jkUtils.ajax;
	delete jkUtils.handleAjaxRsp;
	delete jkUtils.handleAjaxFailed;
	delete jkUtils.showAjaxRequestError;
	delete jkUtils.jkDefaultAjaxOptions.serverDataFilter;
	delete jkUtils.jkDefaultAjaxOptions.networkErrorHandler;
	delete jkUtils.jkDefaultAjaxOptions.serverErrorHandler;
	delete jkUtils.jkDefaultAjaxOptions.reFetchAuth;
	delete jkUtils.jkDefaultAjaxOptions.retry;
	delete jkUtils.jkDefaultAjaxOptions.retryTimeout;
	delete jkUtils.jkDefaultAjaxOptions.errorHandler;
	delete jkUtils.showTip2;
	delete jkUtils.showMessageBox;
	delete jkUtils.showErrMessageBox;
	delete jkUtils.showTipDefaultCfg;
	delete jkUtils._showContextId;

	delete jkUtils.jkGetModuleConfigWithAsync;
	delete jkUtils.jkGetModuleConfig;
	delete jkUtils.jkGetCustomDefined;
	delete jkUtils.jkSaveCustomDefined;
}

window.jkEnums = {
	// 通用操作码
	CommonAction: {
		Add: 1,
		Edit: 2,
		Delete: 3,
		Copy: 4,
		BatchEdit: 5
	},

	// 通用返回码
	CommonRspCode: {
		Success: '200'
	},

	// 声音码
	SoundCode: {
		Success: 0,
		Fail: 1,
		Abnormal: 2,
		Error: 3
	},
	//键盘code
	keyCodeEnum: [{
			keyCode: 48,
			keyName: '0'
		},
		{
			keyCode: 49,
			keyName: '1'
		},
		{
			keyCode: 50,
			keyName: '2'
		},
		{
			keyCode: 51,
			keyName: '3'
		},
		{
			keyCode: 52,
			keyName: '4'
		},
		{
			keyCode: 53,
			keyName: '5'
		},
		{
			keyCode: 54,
			keyName: '6'
		},
		{
			keyCode: 55,
			keyName: '7'
		},
		{
			keyCode: 56,
			keyName: '8'
		},
		{
			keyCode: 57,
			keyName: '9'
		},

		{
			keyCode: 65,
			keyName: 'A'
		},
		{
			keyCode: 66,
			keyName: 'B'
		},
		{
			keyCode: 67,
			keyName: 'C'
		},
		{
			keyCode: 68,
			keyName: 'D'
		},
		{
			keyCode: 69,
			keyName: 'E'
		},
		{
			keyCode: 70,
			keyName: 'F'
		},
		{
			keyCode: 71,
			keyName: 'G'
		},
		{
			keyCode: 72,
			keyName: 'H'
		},
		{
			keyCode: 73,
			keyName: 'I'
		},
		{
			keyCode: 74,
			keyName: 'J'
		},
		{
			keyCode: 75,
			keyName: 'K'
		},
		{
			keyCode: 76,
			keyName: 'L'
		},
		{
			keyCode: 77,
			keyName: 'M'
		},
		{
			keyCode: 78,
			keyName: 'N'
		},
		{
			keyCode: 79,
			keyName: 'O'
		},
		{
			keyCode: 80,
			keyName: 'P'
		},
		{
			keyCode: 81,
			keyName: 'Q'
		},
		{
			keyCode: 82,
			keyName: 'R'
		},
		{
			keyCode: 83,
			keyName: 'S'
		},
		{
			keyCode: 84,
			keyName: 'T'
		},
		{
			keyCode: 85,
			keyName: 'U'
		},
		{
			keyCode: 86,
			keyName: 'V'
		},
		{
			keyCode: 87,
			keyName: 'W'
		},
		{
			keyCode: 88,
			keyName: 'X'
		},
		{
			keyCode: 89,
			keyName: 'Y'
		},
		{
			keyCode: 90,
			keyName: 'Z'
		},

		{
			keyCode: 96,
			keyName: '0'
		},
		{
			keyCode: 97,
			keyName: '1'
		},
		{
			keyCode: 98,
			keyName: '2'
		},
		{
			keyCode: 99,
			keyName: '3'
		},
		{
			keyCode: 100,
			keyName: '4'
		},
		{
			keyCode: 101,
			keyName: '5'
		},
		{
			keyCode: 102,
			keyName: '6'
		},
		{
			keyCode: 103,
			keyName: '7'
		},
		{
			keyCode: 104,
			keyName: '8'
		},
		{
			keyCode: 105,
			keyName: '9'
		},

		{
			keyCode: 106,
			keyName: '*'
		},
		{
			keyCode: 107,
			keyName: '+'
		},
		{
			keyCode: 108,
			keyName: 'Enter'
		},
		{
			keyCode: 109,
			keyName: '-'
		},
		{
			keyCode: 110,
			keyName: '.'
		},
		{
			keyCode: 111,
			keyName: '/'
		},
		{
			keyCode: 112,
			keyName: 'F1'
		},
		{
			keyCode: 113,
			keyName: 'F2'
		},
		{
			keyCode: 114,
			keyName: 'F3'
		},
		{
			keyCode: 115,
			keyName: 'F4'
		},
		{
			keyCode: 116,
			keyName: 'F5'
		},
		{
			keyCode: 117,
			keyName: 'F6'
		},
		{
			keyCode: 118,
			keyName: 'F7'
		},
		{
			keyCode: 119,
			keyName: 'F8'
		},
		{
			keyCode: 120,
			keyName: 'F9'
		},
		{
			keyCode: 121,
			keyName: 'F10'
		},
		{
			keyCode: 122,
			keyName: 'F11'
		},
		{
			keyCode: 123,
			keyName: 'F12'
		},

		{
			keyCode: 8,
			keyName: 'BackSpace'
		},
		{
			keyCode: 9,
			keyName: 'Tab'
		},
		{
			keyCode: 13,
			keyName: 'Enter'
		},
		{
			keyCode: 16,
			keyName: 'Shift'
		},
		{
			keyCode: 17,
			keyName: 'Ctrl'
		},
		{
			keyCode: 18,
			keyName: 'Alt'
		},
		{
			keyCode: 27,
			keyName: 'Esc'
		},
		{
			keyCode: 32,
			keyName: 'Space',
			cname: '空格键'
		},

		{
			keyCode: 37,
			keyName: 'left'
		},
		{
			keyCode: 38,
			keyName: 'up'
		},
		{
			keyCode: 39,
			keyName: 'right'
		},
		{
			keyCode: 40,
			keyName: 'down'
		},
		{
			keyCode: 46,
			keyName: 'Delete'
		},

	]
};

//mini.Form.getData反混淆之后的代码，增加了isArray参数，用来获取数组格式的数据
// 1,时间经过封装不再用getFormValue by caolong
// 2,时间范围选择getdata 指定到结束字段 by caolong
mini.Form.prototype.getData = function(isForm, isDepth, isArray) {
	if (mini.isNull(isDepth)) isDepth = true;
	//todo 切换时间控件后 isForm设置为False
	var getValueHandlerName = 'getValue'; //  isForm ? "getFormValue" : "getValue"
	var fields = this.getFields();
	var result = {};

	for (var i = 0, length = fields.length; i < length; i++) {
		var field = fields[i],
			getValueHandler = field[getValueHandlerName];
		if (!getValueHandler) continue;
		if (field.name) {
			if (isDepth == true)
				mini._setMap(field.name, getValueHandler.call(field), result);
			else
				result[field.name] = getValueHandler.call(field);
		}
		if (field instanceof UserControl.layDate && field._range) {
			var key = field.name,
				endField = field._endField;
			if (endField) {
				var value = result[key].split(' - ');
				if (value.length > 1) {
					result[key] = value[0];
					result[endField] = value[1];
				}
			}
		}
		if (field.textName && field.getText)
			if (isDepth == true)
				mini._setMap(field.textName, field.getText(), result);
			else
				result[field.textName] = field.getText();
		if ((isArray || field.requsetDataType === 'array') && field.multiSelect) {
			var key = field.name || (field.getText && field.textName);
			let item = result[key]
			try {
				if (typeof item === 'number') {
					result[key] = [item]
				} else {
					result[key] = Array.isArray(item) ? item : item.split(',')
					result[key] = result[key] ? result[key].filter(function(value) {
						return value !== '';
					}) : []
				}
			} catch (e) {}
		}
	}
	return result;
};

(function(window) {
	let ajaxLocalCacheQueue = {};
	let requestQueue = {}
	//配置
	let conf = {
		storage: 'sessionStorage',
		cacheNamePrefix: '_ajaxcache'
	};
	$.ajaxSetup({
		beforeSend: function(request, setting) {
			if (setting.repeatCancel || (window[conf.storage] && setting.localCache !== void(0))) {
				var storage = window[conf.storage],
					cacheKey, // 缓存的key值即url+data
					cacheNameSep = ['|', '^', '@', '+', '$'],
					cacheNamePrefix = conf.cacheNamePrefix, // key值拼接
					cacheName,
					cacheDeadline,
					cacheVal;
				if (typeof request.success !== 'function') {
					return console.warn('setting.success error!');
				}
				//获取url
				var url = setting.url,
					data = setting.data,
					delParams = ['timestamp', 'access_token', 'appkey', 'sign', '_'];
				if (url) {
					for (let i = 0; i < delParams.length; i++) {
						url = jkUtils.funcUrlDel(url, delParams[i]); // 过滤掉url后的签名参数
					}
				}
				// 过滤掉data中的签名参数
				if (data) {
					try {
						data = data.split('&').filter(item => !delParams.includes(item.split('=')[0]))
							.join('&');
					} catch (error) {
						console.log(error);
					}
				}
				if (setting.type.toUpperCase() === 'POST') {
					cacheKey = url + '?' + data;
				} else {
					cacheKey = url + (setting.cleanCacheWhenLogOut ? 'jk_cleanCacheWhenLogOut' : '');
				}
				// 处理重复请求
				if (setting.repeatCancel) {
					if (!requestQueue[cacheKey]) {
						requestQueue[cacheKey] = Date.now()
					} else {
						let timeNow = Date.now();
						if (timeNow - requestQueue[cacheKey] <= 1000) {
							console.error('1s之内调用了多次接口:' + cacheKey);
							return request.abort();
						} else {
							requestQueue[cacheKey] = Date.now()
						}
					}
				} else {
					//请求队列
					if (ajaxLocalCacheQueue[cacheKey]) {
						ajaxLocalCacheQueue[cacheKey].push(request.success);
						if (setting.localCache !== 'snapshot') {
							request.ignoreError = true;
							return request.abort();
						}
					}
					//间隔符容错
					$.each(cacheNameSep, function(i, sep) {
						if (cacheKey.indexOf(sep) === -1) {
							cacheNameSep = sep;
							return false;
						}
					});
					if (!cacheNameSep.split) {
						return console.log('url(' + cacheKey + ')包含异常字符无法缓存');
					}
					//查找缓存
					$.each(storage, function(key, val) {
						if (key.indexOf([cacheNamePrefix, cacheKey].join(cacheNameSep) +
								cacheNameSep) === 0) {
							let successTimeStamp = Number(key.split(cacheNameSep)[2]),
								cacheTimeStamp = setting
								.cacheTimeStamp; // cacheTimeStamp : 缓存接口数据时间(毫秒)
							cacheName = key;
							cacheDeadline = cacheTimeStamp ? cacheTimeStamp + successTimeStamp :
								successTimeStamp * 1000;
							cacheVal = val;
							return false;
						}
					});
					if (cacheVal && setting.dataType === 'json') {
						cacheVal = JSON.parse(cacheVal);
					}
					if (setting.localCache && (setting.localCache === 'snapshot' || !isNaN(setting
							.localCache))) {
						var nowDate = new Date().getTime();
						if (cacheDeadline && (cacheDeadline > nowDate)) {
							const resolveWith = request?.deferred?.resolveWith;
							if (jkUtils.jkIsFunction(resolveWith)) {
								request.deferred.resolveWith(request, [cacheVal, 'success', request]);
								return false;
							}
						} else {
							if (cacheDeadline && cacheDeadline <= nowDate) {
								storage.removeItem(cacheName);
							}
							if (typeof(ajaxLocalCacheQueue[cacheKey]) === "undefined") {
								ajaxLocalCacheQueue[cacheKey] = [];
							}
							ajaxLocalCacheQueue[cacheKey].push(request.success);
							this.success = function(res) {
								var newDeadline = new Date().getTime() + setting.localCache,
									newCacheName = [cacheNamePrefix, cacheKey, newDeadline].join(
										cacheNameSep);
								$.each(ajaxLocalCacheQueue[cacheKey], function(i, cb) {
									typeof cb === 'function' && cb(res);
								});
								delete ajaxLocalCacheQueue[cacheKey];
								//缓存数据
								if ($.isPlainObject(res) || $.isArray(res)) {
									if (window.JSON) {
										res = JSON.stringify(res);
									}
								}
								try {
									const parseData = JSON.parse(res);
									parseData?.code === 200 && storage.setItem(newCacheName, res);
								} catch (error) {}
								newDeadline = null;
								newCacheName = null;
							};
							var errorHandle = setting.error || function() {};
							setting.error = function(jqXHR, textStatus, errorThrown) {
								delete ajaxLocalCacheQueue[cacheKey];
								errorHandle(jqXHR, textStatus, errorThrown)
							}
						}
						nowDate = null;
					} else if (cacheName) {
						//清除缓存
						storage.removeItem(cacheName);
					}
				}
			}

			var token = '';
			if (typeof window.get_token === 'function') {
				token = 'Bearer ' + window.get_token();
			} else {
				token = 'Bearer ' + jkUtils.cookie.getCookie('token');
			}
			if (window.location.hostname === 'localhost' || window.location.hostname ===
				'192.168.249.216') {
				var loginInfo = topWindow.loginInfo || jkUtils.getLoginInfo();
				request.setRequestHeader('mem_name', loginInfo.member_name); //mem_name
				request.setRequestHeader('mem_id', loginInfo.member_id);
				request.setRequestHeader('user_id', loginInfo.user_id);
				request.setRequestHeader('user_name', loginInfo.user_name);
				loginInfo.loginResult && request.setRequestHeader('realname', encodeURIComponent(
					loginInfo.loginResult.realName));
				request.setRequestHeader('group_id', 'g001');
			}
			const moduleInfo = jkUtils.jkGetModuleInfo() || {};
			request.setRequestHeader('module_code', moduleInfo.moduleId || '');
			request.setRequestHeader('Authorization', token);
			var ati = jkUtils.getAti();
			if (ati) {
				request.setRequestHeader('ati', ati);
			}
		}
	});
})(window);

$(document).ready(function() {
	// 禁用a标签拖拽
	$('body').on('dragstart', 'a.mini-button', function(ev) {
		ev.preventDefault();
	});

	$('.can-doc-copy').on('selectstart', function(e) {
		e.stopPropagation();
		return true;
	});

	var jkMainFramePage = topWindow.jkMainFramePage;
	if (jkMainFramePage) {
		document.addEventListener('click', function(e) {
			var paths = e.path,
				tdoc = $(topWindow.document),
				searchMenu = tdoc.find("#search-menu")[0],
				searchContextMenu = tdoc.find("#searchContextMenu")[0],
				searchResult = tdoc.find('.search-container');
			// 搜索右键菜单如果存在，就隐藏
			if (searchContextMenu && searchContextMenu.style.display !== 'none') {
				$(searchContextMenu).hide();
			}
			if (paths && paths.indexOf(searchMenu) < 0 && !searchResult.is(":hidden")) {
				if (typeof jkMainFramePage.hideSearchMenu === "function") jkMainFramePage
					.hideSearchMenu();
			}
		}, true);

		// f1
		document.addEventListener('keyup', function(e) {
			if (e.keyCode === 112 && (typeof jkMainFramePage.focusMenuSearch === "function"))
				jkMainFramePage.focusMenuSearch();
		});
	}

	// 当界面缩放的时候，输入界面要始终居中
	$(window).resize(function() {
		if (jkUtils.inputFrame && jkUtils.inputFrame.visible && !jkUtils.inputFrame.destroyed) {
			setTimeout(function() {
				jkUtils.inputFrame.showAtPos('center', 'middle', 'byResize');
			}, 0)
		}
	});
	//创建用于组件验证未通过时的提示用tip
	//判断是否有mini.ToolTip组件(部分引入甘特图的miniui.js无ToolTip)
	if (mini.ToolTip) {
		var tip = new mini.ToolTip();
		//配置tip, 设置检测DOM元素的valid-error
		tip.set({
			target: document,
			selector: '[valid-error]',
			placement: 'bottomleft'
		});
		/**
		 *这里针对创建的提示用tip进行方法覆写, 主要针对getContent方法覆写
		 *原方法悬浮窗的提示内容取自title和data-tooltip
		 *由于自身封装组件多处操作这两个属性, 可能造成显示内容不可控, 这里统一获取元素的valid-error属性值
		 *同样地, 针对验证未通过的组件, 提示对应也要设置在元素的valid-error属性上
		 */
		tip.getContent = function(element) {
			var content;
			//这里置空title, 设置valid-error属性, 避免同时出现两个悬浮效果
			content = jQuery(element).attr("title", "").attr("valid-error");
			return content;
		};
	}
});


mini.VTypes['jknameErrorText'] = jkUtils.jki18n('base_msg_100026', '只支持中文、英文、数字、_符号和-符号', 'base');
mini.VTypes['jkname'] = function(v) {
	var re = new RegExp('^[-A-Za-z0-9\u4e00-\u9fa5_]+$');
	if (re.test(v)) return true;
	return false;
};

mini.VTypes['jktelErrorText'] = jkUtils.jki18n('base_msg_100027', '手机号或电话号不正确！', 'base');
mini.VTypes['jktel'] = function(v) {
	//	var re = /^(1\d{10})|(\d{3,4}(-|\s)?\d{3,14})$/;
	var re1 = /^1\d{10}$/;
	var re2 = /^\d{3,4}(-|\s)?\d{3,14}$/;
	if (!v) return true;
	else if (re1.test(v) || re2.test(v)) return true;
	return false;
};

mini.VTypes['jkrtelErrorText'] = jkUtils.jki18n('base_msg_100028', '手机号格式不正确！', 'base');
mini.VTypes['jkrtel'] = function(v) {
	//	var re = /^(1\d{10})|(\d{3,4}(-|\s)?\d{3,14})$/;
	var re1 = /^1\d{10}$/;
	if (!v) return true;
	else if (re1.test(v)) return true;
	return false;
};

mini.VTypes['jkfaxErrorText'] = jkUtils.jki18n('base_msg_100029', '传真格式不正确！', 'base');
mini.VTypes['jkfax'] = function(v) {
	var re = /^(\d{3,4}-)?\d{7,8}$/;
	if (!v) return true;
	else if (re.test(v)) return true;
	return false;
};

mini.VTypes['jkfloatErrorText'] = jkUtils.jki18n('base_msg_100030', '请输入正数！', 'base');
mini.VTypes['jkfloat'] = function(v) {
	if (!v) return true;
	if (v > 0) return true;
	return false;
};

mini.VTypes['jkcodeErrorText'] = jkUtils.jki18n('base_msg_100031', '请输入由字母或数字、下划线、逗号、小数点组成！', 'base');
mini.VTypes['jkcode'] = function(v) {
	var re = /^[-A-Za-z0-9._,]*$/;
	if (!v) return true;
	else if (re.test(v)) return true;
	return false;
};

mini.VTypes['jkNameErrorText'] = jkUtils.jki18n('base_msg_100032', '请输入由字母或者数字组成，长度小于等于6！', 'base');
mini.VTypes['jkName'] = function(v) {
	var re = /^[-A-Za-z0-9]{1,6}$/;
	if (!v) return true;
	else if (re.test(v)) return true;
	return false;
};

mini.VTypes['jkzipErrorText'] = jkUtils.jki18n('base_msg_100033', '邮政编码格式不正确！', 'base');
mini.VTypes['jkzip'] = function(v) {
	var re = /^[1-9][0-9]{5}$/;
	if (!v) return true;
	else if (re.test(v)) return true;
	return false;
};

mini.VTypes['jkintErrorText'] = jkUtils.jki18n('base_msg_100041', '请输入正整数！', 'base');
mini.VTypes['jkint'] = function(v) {
	var re = /^[1-9]\d*$/;
	if (!v) return true;
	if (re.test(v)) return true;
	return false;
};

mini.VTypes['jkerpcodeErrorText'] = jkUtils.jki18n('base_msg_100034', '必须输入四位数字或英文编码！', 'base');
mini.VTypes['jkerpcode'] = function(v) {
	var regexp = /^[a-zA-Z0-9]{4}$/;
	return regexp.test(v);
};

mini.VTypes['jkcenErrorText'] = jkUtils.jki18n('base_msg_100035', '必须输入大写英文字符！', 'base');
mini.VTypes['jkcen'] = function(v) {
	var regexp = /^[A-Z]+$/;
	return regexp.test(v);
};

mini.VTypes['jkonvolumeErrorText'] = jkUtils.jki18n('base_msg_100036', '必须输入长度*宽度*高度！', 'base');
mini.VTypes['jkonvolume'] = function(v) {
	var regexp = /^[0-9]+(.[0-9]+)?\*[0-9]+(.[0-9]+)?\*[0-9]+(.[0-9]+)?$/;
	if (!v) return true;
	return regexp.test(v);
};

mini.VTypes['jkltfourcodeErrorText'] = jkUtils.jki18n('base_msg_100037', '只能输入字母或数字，长度不能超过4位！', 'base');
mini.VTypes['jkltfourcode'] = function(v) {
	var regexp = /^[a-zA-Z0-9]{1,4}$/;
	return regexp.test(v);
};
// add by zhongbin 2018/7/18
mini.VTypes['jkltsixcodeErrorText'] = jkUtils.jki18n('base_msg_100038', '只能输入字母或数字，长度不能超过6位！', 'base');
mini.VTypes['jkltsixcode'] = function(v) {
	var regexp = /^[a-zA-Z0-9]{1,6}$/;
	return regexp.test(v);
};

mini.VTypes['jkltNumorEncodeErrorText'] = jkUtils.jki18n('base_msg_100039', '必须输入数字或英文编码！', 'base');
mini.VTypes['jkltNumorEncode'] = function(v) {
	var regexp = /^[a-zA-Z0-9]*$/;
	return regexp.test(v);
};

mini.VTypes['jkltYearErrorText'] = jkUtils.jki18n('base_msg_100040', '请输入1949到2100之间的四位年份数字！', 'base');
mini.VTypes['jkltYear'] = function(v) {
	var regexp = /^(1949|19[5-9]\d|20\d{2}|2100)$/;
	return regexp.test(v);
};

mini.jkmask = function(options) {
	var el = mini.byId(options);
	if (mini.isElement(el)) options = {
		el: el
	};
	else if (typeof options == 'string') options = {
		html: options
	};

	options = mini.copyTo({
		html: '',
		cls: '',
		style: '',

		backStyle: ''
	}, options);
	options.el = mini.byId(options.el);
	if (!options.el) options.el = document.body;
	var el = options.el;

	mini['unmask'](options.el);

	el._maskid = mini._MaskID++;
	mini._MaskObjects[el._maskid] = options;


	var maskEl = mini.append(el, '<div class="mini-mask">' +
		'<div class="mini-mask-background" style="' + options.backStyle + '"></div>' +
		'<div class="mini-mask-msg ' + options.cls + '" style="' + options.style + '">' + options.html +
		'</div>' +
		'</div>');
	if (el == document.body) mini.addClass(maskEl, 'mini-fixed');


	options.maskEl = maskEl;
	if (!mini.isNull(options.opacity)) {
		mini.setOpacity(maskEl.firstChild, options.opacity);
	}

	function center() {
		msgEl.style.display = 'block';
		var size = mini.getSize(msgEl);
		msgEl.style.marginLeft = -size.width / 2 + 'px';
		msgEl.style.marginTop = -size.height / 2 + 'px';
		msgEl.style.zIndex = mini.getMaxZIndex();
	}
	var msgEl = maskEl.lastChild;
	msgEl.style.display = 'none';

	setTimeout(function() {
		center();
	}, 0);
	return maskEl;
};

window.onerror = function(message, source, lineno, colno, error) {
	var msgArr = [];
	var template = '{0}:{1}';
	msgArr.push(String.format(template, 'message', message));
	msgArr.push(String.format(template, 'source', source));
	msgArr.push(String.format(template, 'lineno', lineno));
	msgArr.push(String.format(template, 'colno', colno));
	msgArr.push(String.format(template, 'error', error));
	msgArr.push(String.format(template, 'stack', error.stack));
	var result = msgArr.join('\r\n');
	jkUtils.writeLogToShell(result);
};

// 低版本浏览器 兼容数组at方法
if (!Array.prototype.at) {
	Array.prototype.at = function(index) {
		if (index < 0) {
			index += this.length;
		}
		return this[index];
	};
}